#include "turboPmacAxis.h"
#include "asynOctetSyncIO.h"
#include "epicsExport.h"
#include "iocsh.h"
#include "pmacAsynIPPort.h"
#include "turboPmacController.h"
#include <cmath>
#include <errlog.h>
#include <initHooks.h>
#include <limits>
#include <math.h>
#include <string.h>
#include <unistd.h>
#include <vector>

struct turboPmacAxisImpl {
    bool waitForHandshake;
    /*
    This flag is set to true, if the controller is currently enabling /
    disabling the motor and false otherwise. This flag is used in the doPoll
    method to check if the enableRBV PV should be set or not in order to prevent
    a premature change of state (controller reports that the motor is already
    enabled / disabled while it is not ready yet to receive new commands).
    */
    bool enableDisable;
    time_t timeAtHandshake;
    // The axis status is used when enabling / disabling the motor
    int axisStatus;
    bool needInit;
};

/*
Contains all instances of turboPmacAxis which have been created and is used in
the initialization hook function.
 */
static std::vector<turboPmacAxis *> axes;

/**
 * @brief Hook function to perform certain actions during the IOC initialization
 *
 * @param iState
 */
static void epicsInithookFunction(initHookState iState) {
    if (iState == initHookAfterDatabaseRunning) {
        // Iterate through all axes of each and call the initialization method
        // on each one of them.
        for (std::vector<turboPmacAxis *>::iterator itA = axes.begin();
             itA != axes.end(); ++itA) {
            turboPmacAxis *axis = *itA;
            axis->init();
        }
    }
}

turboPmacAxis::turboPmacAxis(turboPmacController *pC, int axisNo,
                             bool initialize)
    : sinqAxis(pC, axisNo), pC_(pC) {

    asynStatus status = asynSuccess;

    if (initialize) {
        // Register the hook function during construction of the first axis
        // object
        if (axes.empty()) {
            initHookRegister(&epicsInithookFunction);
        }

        // Collect all axes into this list which will be used in the hook
        // function
        axes.push_back(this);
    }

    pTurboPmacA_ = std::make_unique<turboPmacAxisImpl>((turboPmacAxisImpl){
        .waitForHandshake = false,
        .enableDisable = false,
        .timeAtHandshake = 0,
        .axisStatus = 0,
        .needInit = false,
    });

    // Provide initial values for some parameter library entries
    status = pC_->setIntegerParam(axisNo_, pC_->rereadEncoderPosition(), 0);
    if (status != asynSuccess) {
        asynPrint(
            pC_->pasynUser(), ASYN_TRACE_ERROR,
            "Controller \"%s\", axis %d  => %s, line %d:\nFATAL ERROR "
            "(setting a parameter value failed with %s)\n. Terminating IOC",
            pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
            pC_->stringifyAsynStatus(status));
        exit(-1);
    }

    // Even though this happens already in sinqAxis, a default value for
    // motorMessageText is set here again, because apparently the sinqAxis
    // constructor is not run before the string is accessed?
    status = setStringParam(pC_->motorMessageText(), "");
    if (status != asynSuccess) {
        asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                  "Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
                  "(setting a parameter value failed "
                  "with %s)\n. Terminating IOC",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                  pC_->stringifyAsynStatus(status));
        exit(-1);
    }

    // turboPmac motors can always be disabled
    status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable(), 1);
    if (status != asynSuccess) {
        pC_->paramLibAccessFailed(status, "motorCanDisable", axisNo_,
                                  __PRETTY_FUNCTION__, __LINE__);
    }

    // Default values for the watchdog timeout mechanism
    setOffsetMovTimeout(30.0); // seconds
    setScaleMovTimeout(2.0);
}

turboPmacAxis::~turboPmacAxis(void) {
    // Since the controller memory is managed somewhere else, we don't need to
    // clean up the pointer pC here.
}

asynStatus turboPmacAxis::init() {

    // Local variable declaration
    asynStatus status = asynSuccess;
    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};
    int nvals = 0;
    double motorRecResolution = 0.0;
    double motorPos = 0.0;
    double motorVelocity = 0.0;
    double motorVmax = 0.0;
    double motorAccel = 0.0;

    // Conversion factor between engineering units (EU) and encoder counts
    double counts_to_eu = 0.0;

    // Deadband in counts. Can be a fraction of a count, hence double.
    double deadband_counts = 0.0;

    // Offset time for the movement watchdog caused by the air cushions in
    // milliseconds.
    int acoDelay = 0.0;

    // The parameter library takes some time to be initialized. Therefore we
    // wait until the status is not asynParamUndefined anymore.
    time_t now = time(NULL);
    time_t maxInitTime = 60;
    while (1) {
        status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
                                     &motorRecResolution);
        if (status == asynParamUndefined) {
            if (now + maxInitTime < time(NULL)) {
                asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                          "Controller \"%s\", axis %d  => %s, line "
                          "%d\nInitializing the parameter library failed.\n",
                          pC_->portName, axisNo_, __PRETTY_FUNCTION__,
                          __LINE__);
                return asynError;
            }
        } else if (status == asynSuccess) {
            break;
        } else if (status != asynSuccess) {
            return pC_->paramLibAccessFailed(status, "motorRecResolution_",
                                             axisNo_, __PRETTY_FUNCTION__,
                                             __LINE__);
        }
    }

    /*
    Read out the axis status, the current position, current and maximum speed,
    acceleration, the air cushion delay, the scaling factor between counts and
    engineering units ([Qxx00] = engineering units / counts) and the deadband
    Ixx65.
    */
    snprintf(command, sizeof(command),
             "Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22 Q%2.2d00 I%2.2d28",
             axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
    status = pC_->writeRead(axisNo_, command, response, 7);

    msgPrintControlKey keyDisconnected = msgPrintControlKey(
        pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, 1);

    if (status != asynSuccess) {
        if (pC_->getMsgPrintControl().shouldBePrinted(keyDisconnected, true,
                                                      pC_->pasynUser())) {
            asynPrint(
                pC_->pasynUser(), ASYN_TRACE_ERROR,
                "Controller \"%s\", axis %d => %s, line %d\nCould not "
                "communicate with controller during IOC initialization (error "
                "%s). Did you use \"drvAsynIPPortConfigure\" instead of "
                "\"pmacAsynIPPortConfigure\" in the .cmd file to create the "
                "port driver?\n",
                pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__,
                pC_->stringifyAsynStatus(status));
        }
        pTurboPmacA_->needInit = true;
        return status;
    }

    // No error has been detected -> Reset the error count
    if (status == asynSuccess) {
        pC_->getMsgPrintControl().resetCount(keyDisconnected, pC_->pasynUser());
    }

    nvals = sscanf(response, "%lf %lf %lf %lf %d %lf %lf", &motorPos,
                   &motorVmax, &motorVelocity, &motorAccel, &acoDelay,
                   &counts_to_eu, &deadband_counts);

    // The acoDelay is given in milliseconds -> Convert to seconds, rounded
    // up
    setOffsetMovTimeout(std::ceil(acoDelay / 1000.0));

    // The PMAC electronic specifies the acceleration in m/s^2. Since we
    // otherwise work with the base length mm, the acceleration is converted
    // here to mm/s^2.
    motorAccel = motorAccel * 1000;

    if (nvals != 7) {
        return pC_->couldNotParseResponse(command, response, axisNo_,
                                          __PRETTY_FUNCTION__, __LINE__);
    }

    // Store these values in the parameter library
    status = setMotorPosition(motorPos);
    if (status != asynSuccess) {
        return status;
    }

    // Initial motor status is idle
    setAxisParamChecked(this, motorStatusDone, 1);

    // Write to the motor record fields
    status = setVeloFields(motorVelocity, 0.0, motorVmax);
    if (status != asynSuccess) {
        return status;
    }
    status = setAcclField(motorAccel);
    if (status != asynSuccess) {
        return status;
    }

    /*
    Check if the new target position is within the range current position
    +/- deadband. If that is the case, no movement command should be sent.
    This functionality is implemented within the motor record itself, we
    just need to populate the field SPDP (see record PushSPDB2Field in
    db/turboPmac.db)

    First, the deadband is read out (Ixx65, see Turbo PMAC User Manual, p.
    160). This value then needs to be converted into counts (by dividing it
    by 16). After words, the deadband in counts then need to be converted
    into engineering units. For this, the scaling factor Qxx10 needs to be
    applied (see SINQ_2G_MCU_SW_manual_rev7, p. 17): [Qxx10] = engineering
    units (eu) / counts deadband_eu = Qxx10 * Ixx65 / 16

    The values of Qxx10 and Ixx65 are read out during initialization and are
    assumed to not change during operation.
    */
    setAxisParamChecked(this, motorPositionDeadband,
                        counts_to_eu * deadband_counts / 16.0);

    // Update the parameter library immediately
    status = callParamCallbacks();
    if (status != asynSuccess) {
        // If we can't communicate with the parameter library, it doesn't
        // make sense to try and upstream this to the user -> Just log the
        // error
        asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                  "Controller \"%s\", axis %d  => %s, line "
                  "%d\ncallParamCallbacks "
                  "failed with %s.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                  pC_->stringifyAsynStatus(status));
        return status;
    }

    status = this->readEncoderType();
    if (status == asynSuccess) {
        pTurboPmacA_->needInit = false;
    }

    return status;
}

// Perform the actual poll
asynStatus turboPmacAxis::doPoll(bool *moving) {

    // Return value for the poll
    asynStatus pollStatus = asynSuccess;

    // Status of read-write-operations of ASCII commands to the controller
    asynStatus status = asynSuccess;

    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};
    char userMessage[pC_->MAXBUF_] = {0};
    int nvals = 0;

    int direction = 0;
    int error = 0;
    int axStatus = 0;
    double currentPosition = 0.0;
    double previousPosition = 0.0;
    double motorRecResolution = 0.0;
    int handshakePerformed = 0;
    double highLimit = 0.0;
    double lowLimit = 0.0;
    double limitsOffset = 0.0;

    // =========================================================================

    if (pTurboPmacA_->needInit) {
        status = init();
        if (status != asynSuccess) {
            return status;
        }
    }

    // Are we currently waiting for a handshake?
    if (pTurboPmacA_->waitForHandshake) {

        // Check if the handshake takes too long and communicate an error in
        // this case. A handshake should not take more than 5 seconds.
        time_t currentTime = time(NULL);
        bool timedOut = (currentTime > pTurboPmacA_->timeAtHandshake + 5);

        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, timedOut,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nAsked for a "
                      "handshake at %ld s and didn't get a positive reply yet "
                      "(current time is %ld s).\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      pTurboPmacA_->timeAtHandshake, currentTime);
        }

        if (timedOut) {
            setAxisParamChecked(this, motorMessageText,
                                "Timed out while waiting for a handshake");
            pTurboPmacA_->waitForHandshake = false;
        }

        snprintf(command, sizeof(command), "P%2.2d23 P%2.2d01", axisNo_,
                 axisNo_);
        status = pC_->writeRead(axisNo_, command, response, 2);
        if (status != asynSuccess) {
            return status;
        }

        nvals = sscanf(response, "%d %d", &handshakePerformed, &error);
        if (nvals != 2) {
            return pC_->couldNotParseResponse(command, response, axisNo_,
                                              __PRETTY_FUNCTION__, __LINE__);
        }

        if (error != 0) {
            // If an error occurred while waiting for the handshake, abort
            // the waiting procedure and continue with the poll in order to
            // handle the error.
            pTurboPmacA_->waitForHandshake = false;
        } else if (handshakePerformed == 1) {
            // Handshake has been performed successfully -> Continue with
            // the poll
            pTurboPmacA_->waitForHandshake = false;
        } else {
            // Still waiting for the handshake - try again in the next busy
            // poll. This is already part of the movement procedure.
            *moving = true;

            setAxisParamChecked(this, motorStatusMoving, *moving);
            setAxisParamChecked(this, motorStatusDone, !(*moving));

            return asynSuccess;
        }
    }

    getAxisParamChecked(this, motorRecResolution, &motorRecResolution);

    // Read the previous motor position
    status = motorPosition(&previousPosition);
    if (status != asynSuccess) {
        return status;
    }

    // Query the axis status
    snprintf(command, sizeof(command),
             "P%2.2d00 Q%2.2d10 P%2.2d01 Q%2.2d13 Q%2.2d14", axisNo_, axisNo_,
             axisNo_, axisNo_, axisNo_);
    status = pC_->writeRead(axisNo_, command, response, 5);
    if (status != asynSuccess) {
        return status;
    }

    nvals = sscanf(response, "%d %lf %d %lf %lf", &axStatus, &currentPosition,
                   &error, &highLimit, &lowLimit);
    if (nvals != 5) {
        return pC_->couldNotParseResponse(command, response, axisNo_,
                                          __PRETTY_FUNCTION__, __LINE__);
    }

    /*
    The axis limits are set as: ({[]})
    where [] are the positive and negative limits set in EPICS/NICOS, {} are
    the software limits set on the MCU and () are the hardware limit
    switches. In other words, the EPICS/NICOS limits should be stricter than
    the software limits on the MCU which in turn should be stricter than the
    hardware limit switches. For example, if the hardware limit switches are
    at [-10, 10], the software limits could be at [-9, 9] and the EPICS /
    NICOS limits could be at
    [-8, 8]. Therefore, we cannot use the software limits read from the MCU
    directly, but need to shrink them a bit. In this case, we're shrinking
    them by limitsOffset on both sides.
    */
    getAxisParamChecked(this, motorLimitsOffset, &limitsOffset);
    highLimit = highLimit - limitsOffset;
    lowLimit = lowLimit + limitsOffset;

    // Store the axis status
    pTurboPmacA_->axisStatus = axStatus;

    // Update the enablement PV, if we are not in the middle of a enabling /
    // disabling procedure.
    if (!(pTurboPmacA_->enableDisable)) {
        setAxisParamChecked(
            this, motorEnableRBV,
            (axStatus != -3 && axStatus != -4 && axStatus != -5));
    }

    // Create the unique callsite identifier manually so it can be used
    // later in the shouldBePrinted calls.
    msgPrintControlKey keyStatus = msgPrintControlKey(
        pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
    bool resetCountStatus = true;

    // Interpret the status
    switch (axStatus) {
    case -6:
        // Axis is stopping
        *moving = true;
        break;
    case -5:
        // Axis is deactivated
        *moving = false;
        setAxisParamChecked(this, motorMessageText,
                            "Deactivated. Please call the support.");
        pollStatus = asynError;
        break;
    case -4:
        // Emergency stop
        *moving = false;

        if (pC_->getMsgPrintControl().shouldBePrinted(keyStatus, true,
                                                      pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nEmergency "
                      "stop activated.%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      pC_->getMsgPrintControl().getSuffix());
        }
        resetCountStatus = false;
        setAxisParamChecked(
            this, motorMessageText,
            "Emergency stop. Reset SPS or check emergency stop buttons.");
        pollStatus = asynError;
        break;
    case -3:
        // Disabled
        *moving = false;
        break;
    case 0:
        // Idle
        *moving = false;
        break;
    case 1:
        // Move order acknowledged
        *moving = true;
        break;
    case 2:
        // Move order confirmed possible
        *moving = true;
        break;
    case 3:
        // Axis in Air Cushion Output status
        *moving = true;
        break;
    case 4:
        // Axis in Air Cushion Input status
        *moving = true;
        break;
    case 5:
        // Motor is moving normally
        *moving = true;
        break;
    case 6:
        // Jog start
        *moving = true;
        break;
    case 7:
        // Jog forward
        *moving = true;
        break;
    case 8:
        // Jog backward
        *moving = true;
        break;
    case 9:
        // Jog break
        *moving = true;
        break;
    case 10:
        // Jog step forward
        *moving = true;
        break;
    case 11:
        // Jog step backward
        *moving = true;
        break;
    case 12:
        // Jog stop
        *moving = true;
        break;
    case 13:
        // Backlash
        *moving = true;
        break;
    case 14:
        // Move order out of range - this is also handled as an error
        *moving = false;
        break;
    case 15:
        // Move is interrupted, but the motor is not ready to receive
        // another move command. Therefore it is treated as still moving.
        *moving = true;
        break;
    case 16:
        // Move is resumed
        *moving = true;
        break;
    default:
        *moving = false;

        if (pC_->getMsgPrintControl().shouldBePrinted(keyStatus, true,
                                                      pC_->pasynUser())) {

            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nUnknown "
                      "state P%2.2d00 = %d.%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      axisNo_, axStatus, pC_->getMsgPrintControl().getSuffix());
        }
        resetCountStatus = false;

        snprintf(
            userMessage, sizeof(userMessage),
            "Reached unknown state P%2.2d00 = %d. Please call the support.",
            axisNo_, error);
        setAxisParamChecked(this, motorMessageText, userMessage);
    }

    if (resetCountStatus) {
        pC_->getMsgPrintControl().resetCount(keyStatus, pC_->pasynUser());
    }

    if (*moving) {
        // If the axis is moving, evaluate the movement direction
        if ((currentPosition - previousPosition) > 0) {
            direction = 1;
        } else {
            direction = 0;
        }
    }

    asynStatus errorStatus =
        handleError(error, userMessage, sizeof(userMessage));
    if (errorStatus != asynSuccess) {
        pollStatus = errorStatus;
    }

    if (*moving == false) {
        setAxisParamChecked(this, motorMoveToHome, false);
    }

    setAxisParamChecked(this, motorStatusMoving, *moving);
    setAxisParamChecked(this, motorStatusDone, !(*moving));
    setAxisParamChecked(this, motorStatusDirection, direction);

    int limFromHardware = 0;
    getAxisParamChecked(this, limFromHardware, &limFromHardware);

    if (limFromHardware != 0) {
        setAxisParamChecked(this, motorHighLimitFromDriver, highLimit);
        setAxisParamChecked(this, motorLowLimitFromDriver, lowLimit);
    }

    status = setMotorPosition(currentPosition);
    if (status != asynSuccess) {
        return status;
    }
    return pollStatus;
}

asynStatus turboPmacAxis::handleError(int error, char *userMessage,
                                      int sizeUserMessage) {
    asynStatus status = asynError;

    switch (error) {
    case 0:
        status = asynSuccess;
        // No error -> Reset the message repetition watchdog
        break;
    case 1:
        // EPICS should already prevent this issue in the first place,
        // since it contains the user limits
        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nTarget "
                      "position would exceed user limits.%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      pC_->getMsgPrintControl().getSuffix());
        }
        setAxisParamChecked(this, motorMessageText,
                            "Target position would exceed software limits.");
        break;
    case 5:
        // Command not possible
        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nAxis is "
                      "still moving, but received another move command. EPICS "
                      "should prevent this, check if *moving is set "
                      "correctly.%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      pC_->getMsgPrintControl().getSuffix());
        }
        setAxisParamChecked(
            this, motorMessageText,
            "Axis received move command while it is not ready.");
        break;
    case 8:
        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(
                pC_->pasynUser(), ASYN_TRACE_ERROR,
                "Controller \"%s\", axis %d  => %s, line %d\nLost feedback "
                "from auxiliary device during movement (P%2.2d01 = "
                "%d).%s\n",
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
                error, pC_->getMsgPrintControl().getSuffix());
        }
        snprintf(userMessage, sizeUserMessage,
                 "Lost feedback from auxiliary device during movement "
                 "(P%2.2d01 = "
                 "%d). Please call the support.",
                 axisNo_, error);
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;
    case 9:
        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nNo feedback "
                      "from "
                      "auxiliary device before movement (P%2.2d01 = %d).%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      axisNo_, error, pC_->getMsgPrintControl().getSuffix());
        }

        snprintf(
            userMessage, sizeUserMessage,
            "No feedback from auxiliary device before movement (P%2.2d01 = "
            "%d). Please call the support.",
            axisNo_, error);
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;
    case 10:
        /*
        Software limits of the controller have been hit. Since the EPICS
        limits are derived from the software limits and are a little bit
        smaller, this error case can only happen if either the axis has an
        incremental encoder which is not properly homed or if a bug occured.
        */

        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(
                pC_->pasynUser(), ASYN_TRACE_ERROR,
                "Controller \"%s\", axis %d  => %s, line %d\nAxis hit the "
                "controller limits.%s\n",
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                pC_->getMsgPrintControl().getSuffix());
        }
        snprintf(userMessage, sizeUserMessage,
                 "Software limits or end switch hit (P%2.2d01 = %d). Try "
                 "homing the motor, moving in the opposite direction or check "
                 "the SPS for errors (if available). "
                 "Otherwise please call the support.",
                 axisNo_, error);
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;
    case 11:
        // Following error

        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nMaximum "
                      "allowed "
                      "following error exceeded.%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      pC_->getMsgPrintControl().getSuffix());
        }
        snprintf(userMessage, sizeUserMessage,
                 "Maximum allowed following error exceeded (P%2.2d01 = %d). "
                 "Check if movement range is blocked. "
                 "Otherwise please call the support.",
                 axisNo_, error);
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;

    case 12:
        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nSecurity "
                      "input is triggered (P%2.2d01 = %d).%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      axisNo_, error, pC_->getMsgPrintControl().getSuffix());
        }
        snprintf(userMessage, sizeUserMessage,
                 "Security input is triggered (P%2.2d01 = %d). Check the SPS "
                 "for errors (if available). Otherwise please call "
                 "the support.",
                 axisNo_, error);
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;

    case 13:
        // Driver hardware error triggered

        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nDriver "
                      "hardware error triggered.%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      pC_->getMsgPrintControl().getSuffix());
        }
        snprintf(userMessage, sizeUserMessage,
                 "Driver hardware error (P%2.2d01 = 13). Please call the "
                 "support.",
                 axisNo_);
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;
    case 14:
        // EPICS should already prevent this issue in the first place,
        // since it contains the user limits
        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                      "Controller \"%s\", axis %d  => %s, line %d\nMove "
                      "command exceeds hardware limits (P%2.2d01 = %d).%s\n",
                      pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                      axisNo_, error, pC_->getMsgPrintControl().getSuffix());
        }
        snprintf(userMessage, sizeUserMessage,
                 "Move command exceeds hardware limits (P%2.2d01 = %d). Please "
                 "call the support.",
                 axisNo_, error);
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;
    default:

        if (pC_->getMsgPrintControl().shouldBePrinted(
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
                pC_->pasynUser())) {
            asynPrint(
                pC_->pasynUser(), ASYN_TRACE_ERROR,
                "Controller \"%s\", axis %d  => %s, line %d\nUnknown error "
                "P%2.2d01 = %d.%s\n",
                pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
                error, pC_->getMsgPrintControl().getSuffix());
        }
        setAxisParamChecked(this, motorMessageText, userMessage);
        break;
    }
    return status;
}

asynStatus turboPmacAxis::doMove(double position, int relative,
                                 double minVelocity, double maxVelocity,
                                 double acceleration) {

    // Status of read-write-operations of ASCII commands to the controller
    asynStatus status = asynSuccess;

    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};
    double motorTargetPosition = 0.0;
    double motorRecResolution = 0.0;
    double motorVelocity = 0.0;
    int enabled = 0;
    int motorCanSetSpeed = 0;
    int writeOffset = 0;

    // =========================================================================

    // Suppress unused variables warning
    (void)minVelocity;
    (void)acceleration;

    getAxisParamChecked(this, motorEnableRBV, &enabled);
    getAxisParamChecked(this, motorRecResolution, &motorRecResolution);

    if (enabled == 0) {
        asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                  "Controller \"%s\", axis %d  => %s, line %d\nAxis is "
                  "disabled.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
        return asynSuccess;
    }

    // Convert from EPICS to user / motor units
    motorTargetPosition = position * motorRecResolution;
    motorVelocity = maxVelocity * motorRecResolution;

    asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
              "Controller \"%s\", axis %d  => %s, line %d\nStart of axis to "
              "position %lf.\n",
              pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
              motorTargetPosition);

    // Check if the speed is allowed to be changed
    getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed);

    // Prepend the new motor speed, if the user is allowed to set the speed.
    // Mind the " " (space) before the closing "", as the command created
    // here is prepended to the one down below.
    if (motorCanSetSpeed != 0) {
        snprintf(command, sizeof(command), "Q%2.2d04=%lf ", axisNo_,
                 motorVelocity);
        writeOffset = strlen(command);

        asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
                  "Controller \"%s\", axis %d  => %s, line %d\nSetting speed "
                  "to %lf.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                  motorVelocity);
    }

    // Perform handshake, Set target position (and speed, if allowed) and
    // start the move command
    if (relative) {
        snprintf(&command[writeOffset], sizeof(command) - writeOffset,
                 "P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", axisNo_, axisNo_,
                 motorTargetPosition, axisNo_);
    } else {
        snprintf(&command[writeOffset], sizeof(command) - writeOffset,
                 "P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", axisNo_, axisNo_,
                 motorTargetPosition, axisNo_);
    }

    // We don't expect an answer
    status = pC_->writeRead(axisNo_, command, response, 0);
    if (status != asynSuccess) {

        asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                  "Controller \"%s\", axis %d  => %s, line %d\nStarting "
                  "movement to "
                  "target position %lf failed.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                  motorTargetPosition);
        setAxisParamChecked(this, motorStatusProblem, true);
        return status;
    }

    // In the next poll, we will check if the handshake has been performed
    // in a reasonable time
    pTurboPmacA_->waitForHandshake = true;
    pTurboPmacA_->timeAtHandshake = time(NULL);

    // Waiting for a handshake is already part of the movement procedure =>
    // Start the watchdog
    if (startMovTimeoutWatchdog() != asynSuccess) {
        return asynError;
    }

    return status;
}

asynStatus turboPmacAxis::stop(double acceleration) {

    asynStatus status = asynSuccess;

    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};

    // =========================================================================

    // Suppress unused variables warning
    (void)acceleration;

    snprintf(command, sizeof(command), "M%2.2d=8", axisNo_);
    status = pC_->writeRead(axisNo_, command, response, 0);

    if (status != asynSuccess) {
        asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                  "Controller \"%s\", axis %d  => %s, line %d\nStopping "
                  "the movement "
                  "failed.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
        setAxisParamChecked(this, motorStatusProblem, true);
    }

    // Reset the driver to idle state and move out of the handshake wait
    // loop, if we're currently inside it.
    pTurboPmacA_->waitForHandshake = false;

    /*
    Stopping the motor results in a movement and further move commands have
    to wait until the stopping movement is done. Therefore, we need to wait
    until the poller "sees" the changed state (otherwise, we risk issuing
    move commands while the motor is stopping). To ensure that at least one
    poll is done, this thread (which also runs move commands) is paused for
    twice the idle poll period.
    */
    unsigned int idlePollMicros =
        (unsigned int)ceil(pC_->idlePollPeriod() * 1e6);
    usleep(2 * idlePollMicros);

    return status;
}

asynStatus turboPmacAxis::doReset() {

    // Status of read-write-operations of ASCII commands to the controller
    asynStatus status = asynSuccess;

    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};

    // =========================================================================

    // Reset the error for this axis manually
    snprintf(command, sizeof(command), "P%2.2d01=0", axisNo_);
    status = pC_->writeRead(axisNo_, command, response, 0);

    if (status != asynSuccess) {
        asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                  "Controller \"%s\", axis %d  => %s, line %d\nResetting the "
                  "error failed\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
        setAxisParamChecked(this, motorStatusProblem, true);
    }

    // Reset the driver to idle state and move out of the handshake wait
    // loop, if we're currently inside it.
    pTurboPmacA_->waitForHandshake = false;

    // Disable the axis
    return enable(false);
}

/*
Home the axis. On absolute encoder systems, this is a no-op
*/
asynStatus turboPmacAxis::doHome(double minVelocity, double maxVelocity,
                                 double acceleration, int forwards) {

    // Status of read-write-operations of ASCII commands to the controller
    asynStatus status = asynSuccess;

    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};

    // =========================================================================

    // Suppress unused variables warning
    (void)minVelocity;
    (void)maxVelocity;
    (void)acceleration;
    (void)forwards;

    getAxisParamChecked(this, encoderType, &response);

    // Only send the home command if the axis has an incremental encoder
    if (strcmp(response, IncrementalEncoder) == 0) {

        snprintf(command, sizeof(command), "M%2.2d=9", axisNo_);
        status = pC_->writeRead(axisNo_, command, response, 0);
        if (status != asynSuccess) {
            return status;
        }

        setAxisParamChecked(this, motorMoveToHome, true);
        return callParamCallbacks();
    }

    return asynSuccess;
}

/*
Read the encoder type and update the parameter library accordingly
*/
asynStatus turboPmacAxis::readEncoderType() {

    // Status of read-write-operations of ASCII commands to the controller
    asynStatus status = asynSuccess;

    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};
    int nvals = 0;
    int encoder_id = 0;

    // =========================================================================

    // Check if this is an absolute encoder
    snprintf(command, sizeof(command), "I%2.2d04", axisNo_);
    status = pC_->writeRead(axisNo_, command, response, 1);
    if (status != asynSuccess) {
        return status;
    }

    int reponse_length = strlen(response);
    if (reponse_length < 3) {
        return pC_->couldNotParseResponse(command, response, axisNo_,
                                          __PRETTY_FUNCTION__, __LINE__);
    }

    // We are only interested in the last two digits and the last value in
    // the string before the terminator is \r
    nvals = sscanf(response + (reponse_length - 3), "%2X", &encoder_id);
    if (nvals != 1) {
        return pC_->couldNotParseResponse(command, response, axisNo_,
                                          __PRETTY_FUNCTION__, __LINE__);
    }

    snprintf(command, sizeof(command), "P46");
    status = pC_->writeRead(axisNo_, command, response, 1);
    if (status != asynSuccess) {
        return status;
    }
    int number_of_axes = strtol(response, NULL, 10);

    // If true, the encoder is incremental
    if (encoder_id <= number_of_axes) {
        setAxisParamChecked(this, encoderType, IncrementalEncoder);
    } else {
        setAxisParamChecked(this, encoderType, AbsoluteEncoder);
    }
    return asynSuccess;
}

/*
This is not a command that can always be run when enabling a motor as it
also causes relative encoders to reread a position necessitating
recalibration. We only want it to run on absolute encoders. We also want it
to be clear to instrument scientists, that power has to be cut to the motor,
in order to reread the encoder as not all motors have breaks and they may
start to move when disabled. For that reason, we don't automatically disable
the motors to run the command and instead require that the scientists first
disable the motor.
*/
asynStatus turboPmacAxis::rereadEncoder() {
    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};
    char encoderType[pC_->MAXBUF_] = {0};

    // Status of read-write-operations of ASCII commands to the controller
    asynStatus status = asynSuccess;

    // =========================================================================

    // Check if this is an absolute encoder
    status = readEncoderType();
    if (status != asynSuccess) {
        return status;
    }
    getAxisParamChecked(this, encoderType, &encoderType);

    // Abort if the axis is incremental
    if (strcmp(encoderType, IncrementalEncoder) == 0) {
        asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
                  "Controller \"%s\", axis %d  => %s, line %d\nEncoder is "
                  "not reread because it is incremental.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
        return asynSuccess;
    }

    // Check if the axis is disabled. If not, inform the user that this
    // is necessary
    int enabled = 0;
    getAxisParamChecked(this, motorEnableRBV, &enabled);

    if (enabled == 1) {
        asynPrint(pC_->pasynUser(), ASYN_TRACE_WARNING,
                  "Controller \"%s\", axis %d  => %s, line %d\nAxis must be "
                  "disabled before rereading the encoder.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
        setAxisParamChecked(
            this, motorMessageText,
            "Axis must be disabled before rereading the encoder.");
        return asynError;
    } else {
        snprintf(command, sizeof(command), "M%2.2d=15", axisNo_);
        asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
                  "Controller \"%s\", axis %d  => %s, line %d\nRereading "
                  "absolute "
                  "encoder via command %s.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                  command);
        pC_->writeRead(axisNo_, command, response, 0);
    }

    // Switching on the axis while the rereading process is still
    // ongoing causes it to fail. We currently have no way to check if
    // it is actually finished, so we instead wait for 0.5 seconds.
    usleep(500000);

    // Turn off parameter as finished rereading, this will only be
    // immediately noticed in the read back variable though
    setAxisParamChecked(this, rereadEncoderPosition, false);
    return asynSuccess;
}

asynStatus turboPmacAxis::enable(bool on) {

    int timeout_enable_disable = 5;
    char command[pC_->MAXBUF_] = {0};
    char response[pC_->MAXBUF_] = {0};
    int nvals = 0;

    // Status of read-write-operations of ASCII commands to the controller
    asynStatus status = asynSuccess;

    // =========================================================================

    /*
    Continue regardless of the status returned by the poll; we just want to
    find out whether the motor is currently moving or not. If the poll
    function fails before it can determine that, it is assumed that the
    motor is not moving.
    */
    bool moving = false;
    doPoll(&moving);

    // If the axis is currently moving, it cannot be disabled. Ignore the
    // command and inform the user. We check the last known status of the
    // axis instead of "moving", since status -6 is also moving, but the
    // motor can actually be disabled in this state!
    int axStatus = pTurboPmacA_->axisStatus;
    if (axStatus == 1 || axStatus == 2 || axStatus == 3 || axStatus == 4 ||
        axStatus == 5 || axStatus == 6 || axStatus == 7 || axStatus == 8 ||
        axStatus == 9 || axStatus == 10 || axStatus == 11 || axStatus == 12 ||
        axStatus == 13 || axStatus == 15 || axStatus == 16) {

        asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
                  "Controller \"%s\", axis %d  => %s, line %d\nAxis is not "
                  "idle (status %d) and can therefore not be enabled / "
                  "disabled.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                  axStatus);

        setAxisParamChecked(this, motorMessageText,
                            "Axis cannot be disabled while it is moving.");
        pTurboPmacA_->enableDisable = false;
        return asynError;
    }

    // Axis is already enabled / disabled and a new enable / disable command
    // was sent => Do nothing
    if ((axStatus != -3) == on) {
        asynPrint(pC_->pasynUser(), ASYN_TRACE_WARNING,
                  "Controller \"%s\", axis %d  => %s, line %d\nAxis is "
                  "already %s.\n",
                  pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
                  on ? "enabled" : "disabled");
        return asynSuccess;
    }

    // Reread the encoder, if the axis is going to be enabled
    if (on != 0) {
        status = rereadEncoder();
        if (status != asynSuccess) {
            return status;
        }
    }

    // Now the actual enabling / disabling starts
    pTurboPmacA_->enableDisable = true;

    // Enable / disable the axis
    snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on);
    asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
              "Controller \"%s\", axis %d  => %s, line %d\n%s axis\n",
              pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
              on ? "Enable" : "Disable");

    status = pC_->writeRead(axisNo_, command, response, 0);
    if (status != asynSuccess) {
        return status;
    }

    // Query the axis status every few milliseconds until the axis has been
    // enabled or until the timeout has been reached
    snprintf(command, sizeof(command), "P%2.2d00", axisNo_);
    int startTime = time(NULL);
    while (time(NULL) < startTime + timeout_enable_disable) {

        // Wait a bit between status updates from the controller.
        usleep(10000);

        // Read the axis status
        status = pC_->writeRead(axisNo_, command, response, 1);
        if (status != asynSuccess) {
            // Enabling / disabling procedure failed
            pTurboPmacA_->enableDisable = false;
            return status;
        }
        nvals = sscanf(response, "%d", &pTurboPmacA_->axisStatus);
        if (nvals != 1) {
            return pC_->couldNotParseResponse(command, response, axisNo_,
                                              __PRETTY_FUNCTION__, __LINE__);
        }

        if ((pTurboPmacA_->axisStatus != -3) == on) {
            usleep(500000);

            bool moving = false;
            forcedPoll(&moving);

            // Enabling / disabling procedure is completed (successfully)
            pTurboPmacA_->enableDisable = false;
            return asynSuccess;
        }
    }

    // Failed to change axis status within timeout_enable_disable => Send a
    // corresponding message
    asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
              "Controller \"%s\", axis %d  => %s, line %d\nFailed to %s axis "
              "within %d seconds\n",
              pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
              on ? "enable" : "disable", timeout_enable_disable);

    // Output message to user
    snprintf(command, sizeof(command), "Failed to %s within %d seconds",
             on ? "enable" : "disable", timeout_enable_disable);
    setAxisParamChecked(this, motorMessageText, command);

    // Enabling / disabling procedure failed
    pTurboPmacA_->enableDisable = false;
    return asynError;
}

bool turboPmacAxis::needInit() { return pTurboPmacA_->needInit; }

/**
 * @brief Instruct the axis to run its init() function during the next poll
 *
 * @param needInit
 */
void turboPmacAxis::setNeedInit(bool needInit) {
    pTurboPmacA_->needInit = needInit;
}

/*************************************************************************************/
/** The following functions are C-wrappers, and can be called directly from
 * iocsh */

extern "C" {

/*
C wrapper for the axis constructor. Please refer to the turboPmacAxis
constructor documentation. The controller is read from the portName.
*/
asynStatus turboPmacCreateAxis(const char *portName, int axis) {

    /*
    findAsynPortDriver is a asyn library FFI function which uses the C ABI.
    Therefore it returns a void pointer instead of e.g. a pointer to a
    superclass of the controller such as asynPortDriver. Type-safe upcasting
    via dynamic_cast is therefore not possible directly. However, we do know
    that the void pointer is either a pointer to asynPortDriver (if a driver
    with the specified name exists) or a nullptr. Therefore, we first do a
    nullptr check, then a cast to asynPortDriver and lastly a (typesafe)
    dynamic_upcast to Controller
    https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
     */
    void *ptr = findAsynPortDriver(portName);
    if (ptr == nullptr) {
        /*
        We can't use asynPrint here since this macro would require us
        to get an asynUser from a pointer to an asynPortDriver.
        However, the given pointer is a nullptr and therefore doesn't
        have an asynUser! printf is an EPICS alternative which
        works w/o that, but doesn't offer the comfort provided
        by the asynTrace-facility
        */
        errlogPrintf("Controller \"%s\" => %s, line %d\nPort not found.",
                     portName, __PRETTY_FUNCTION__, __LINE__);
        return asynError;
    }
    // Unsafe cast of the pointer to an asynPortDriver
    asynPortDriver *apd = (asynPortDriver *)(ptr);

    // Safe downcast
    turboPmacController *pC = dynamic_cast<turboPmacController *>(apd);
    if (pC == nullptr) {
        errlogPrintf("Controller \"%s\" => %s, line %d\nController "
                     "is not a turboPmacController.",
                     portName, __PRETTY_FUNCTION__, __LINE__);
        return asynError;
    }

    // Prevent manipulation of the controller from other threads while we
    // create the new axis.
    pC->lock();

    /*
    We create a new instance of the axis, using the "new" keyword to
    allocate it on the heap while avoiding RAII.
    https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
    https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp

    The created object is registered in EPICS in its constructor and can
    safely be "leaked" here.
    */
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
    turboPmacAxis *pAxis = new turboPmacAxis(pC, axis);

    // Allow manipulation of the controller again
    pC->unlock();
    return asynSuccess;
}

/*
Same procedure as for the CreateController function, but for the axis
itself.
*/
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)",
                                        iocshArgString};
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
                                                 &CreateAxisArg1};
static const iocshFuncDef configTurboPmacCreateAxis = {
    "turboPmacAxis", 2, CreateAxisArgs,
    "Create an instance of a turboPmac axis. The first argument is the "
    "controller this axis should be attached to, the second argument is "
    "the "
    "axis number."};
static void configTurboPmacCreateAxisCallFunc(const iocshArgBuf *args) {
    turboPmacCreateAxis(args[0].sval, args[1].ival);
}

// This function is made known to EPICS in turboPmac.dbd and is called by
// EPICS in order to register both functions in the IOC shell
static void turboPmacAxisRegister(void) {
    iocshRegister(&configTurboPmacCreateAxis,
                  configTurboPmacCreateAxisCallFunc);
}
epicsExportRegistrar(turboPmacAxisRegister);

} // extern "C"
