EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026 Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026
<== Date ==> <== Thread ==>

Subject: Re: Support for piezo controller nanoFAKTUR EBD-060310
From: LiangChih Chiang via Tech-talk <tech-talk at aps.anl.gov>
To: Torsten Bögershausen <tboegi at edom.se>
Cc: "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>
Date: Tue, 10 Feb 2026 15:51:07 +0800
Hello, EPICS mates, thanks for the help.

The Model 3 driver implementation of nanoFaktur EBD-060310 piezo controller is kind of working now.
The attachment files include source code.and IOC startup files.

I still have some questions. Hope you can provide suggestions.


1.
I thought motorClosedLoop_ is mapped to CNEN, isn't it?

I set  motorClosedLoop_ to 1 in code, as following:
setIntegerParam(c_p_->motorClosedLoop_, 1);

But in the CNEN field of motor record is 0(Disable).


2.
Can I set UEIP field of motor record in driver code?

If UEIP is 0, the RRBV won't update even REP is updated continuously. 

I use the asyn_motor.db in motor-R6-11.
The motor record in this .db doesn't have UEIP field.
So I set it in a .doAfterIocInit file.


3.
The MRES field of motor record to 0.000001 in ioc startup file.

When I set VAL of motor record to 1.234,
I will get "position" value 1234000
in asynMotorAxis::move(double position, int relative, double minVelocity, double maxVelocity, double acceleration);

Then I need the MRES value to do the conversion,
because I need to send the value "1.234" to the controller.

Why the following code returns the value 0 instead the wanted 0.000001?
    double res = 0.000001;  // resolution
    c_p_->getDoubleParam(channel_, c_p_->motorResolution_, &res);


4.
There is no "home" concept in this piezo controller.
So I have no way to implement the asynMotorAxis::home function, am I right?





On Sat, Feb 7, 2026 at 12:08 AM Torsten Bögershausen <tboegi at edom.se> wrote:
Hej again,

Why are the RMP and REP fields integer ?
The "real world encoders" typically count in integer,
and so far the int32 had been enough.
Even if we today have encoders with more bits,
these are typically attached to the motor controller
and handled there.

The RMP field counts steps.
It was designed for a stepper motor, and even here
32 bit where both enough
(and that is what we have before int64 became available)
The motorRecord itself uses the step in RMP to prevent
commanding a motion the will end up on the same step.
Moving from 0.050 to 0.051 is prevented, when MRES is 0.05

For a piezo (or other modern controllers) we don't need this
step-size, so that the motorRecord has  got the SPDB field,
Set Point Dead Band.

For the controllers which are in use here, most of them
communicate in EGU.

For that reason I made a patch, where the value
"motorPosition_" from the driver is copied into
the DRBV field without conversion through integers.

The driver needs this patch:
<https://github.com/EuropeanSpallationSource/m-epics-ethercatmc/blob/master/ethercatmcApp/src/ethercatmcIndexerAxis.cpp#L81..#L83>


The motor package itself is here:
<https://github.com/EuropeanSpallationSource/motor/releases/tag/v7.2.8.1-ESS>

BR
/Torsten


On 2026-02-06 14:20, LiangChih Chiang wrote:
> Hi, Torsten, thanks for replying.
>
>  >Using epicsPrintf (or even printf) should be forbidden in
>  >these kind of driver code.
> ok. I'll fix it.
>
>  >>1.
>  > pC_->motorPosition_ is an int, used as an index
>  > motorPosition is a double
> You're right. My mistake.
>
> Another question follows:
> "cainfo <motor_record>.REP and .RMP" will get DBF_LONG.
> Why aren't they DBF_DOUBLE?
>
>
>  >>3.
>  >>The values of position and voltage from the controller are float values.
>  >>When setting, say 2.123456, to motorEncoderPosition, it will become 2
> (which is an int).
>  >This is depending on MRES in the motorRecord.
>  >If you set it to 1.0 (or not at all, the the fallback is 1.0)
>  >all values are rounded into integer.
>  >The trick is to set MRES to 0.000001 or so and compensate
>  >this in the driver (multipy with 1000000)
>
> Yes. You're right.
> I'll try.
>
>
>  >For the open-loop-voltage:
>  >You probably want an own asyn-parameter for this.
> I'll consider it.
> However, the chance of this piezo controller in open-loop mode is
> extremely slim.
>
> How to support two modes(closed-loop and open-loop) in Model 3 driver?
> In closed-loop mode, VAL and RBV mean position(unit um)?
> In open-loop mode, VAL and RBV mean voltage?
>
> On Fri, Feb 6, 2026 at 5:51 PM Torsten Bögershausen <tboegi at edom.se
> <mailto:tboegi at edom.se>> wrote:
>
>     Hej LiangChih Chiang,
>     thanks for asking.
>
>     Before going into your questions, please allow one comment:
>     Using epicsPrintf (or even printf) should be forbidden in
>     these kind of driver code.
>     Please consider to us asynPrint instead.
>     Especially things like
>     #ifdef DEBUG
>                 printf("Warning: un-handled param data format:
>     #endif
>     Could be better coded as
>         asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
>             "%s: Warning....\n",
>             __FUNCTION__);
>         }
>
>     (If you write Warning or Err: or something is more a matter of taste)
>     But in any way you want to know if things go bad much later,
>     after firmware updates, changes in the IOC, glitches here and the )
>     Having said that, finishing my review, which may read unintentionally
>     hard, lets digg into the questions.
>
>        1.
>       > In asynMotorController.h, the data type of motorPosition_
>       > and motorEncoderPosition_ are ints.
>
>     A call inside the axis class looks like this:
>
>     asynMotorAxis::setDoubleParam(pC_->motorPosition_, motorPosition);
>
>     pC_->motorPosition_ is an int,
>     saying which asynparameter we ar writing to.
>
>     motorPosition is a double, saying where the motor should be,
>     according to the controller.
>
>     motorEncoderPosition_ is the value coming from the sensor inside the
>     controller.
>
>     Example:
>     the MCS2 from Smaract reads motorPosition like this:
>        // Read the target position
>         snprintf(pC_->outString_,sizeof(pC_->outString_)-1,
>           ":CHAN%d:POS:TARG?", axisNo_);
>
>     And encoderPosition like this:
>            // read the actual position as measured by the sensor
>             snprintf(pC_->outString_,sizeof(pC_->outString_)-1,
>             ":CHAN%d:POS?", axisNo_);
>
>     3.
>       > The values of position and voltage from the controller are float
>     values.
>       > When setting, say 2.1234...
>     This is depending on MRES in the motorRecord.
>     If you set it to 1.0 (or not at all, the the fallback is 1.0)
>     all values are rounded into integer.
>     The trick is to set MRES to 0.000001 or so and compensate
>     this in the driver (multipy with 1000000)
>
>
>     For the open-loop-voltage:
>     You probably want an own asyn-parameter for this.
>
>     /BR Torsten
>
>
>
>     On 2026-02-06 08:48, LiangChih Chiang via Tech-talk wrote:
>      > Hello, EPICS mates,
>      >
>      > I started to implement the “Model 3” driver for nanoFaktur
>     EBD-060310
>      > piezo controller.
>      > The attached files(.cpp and .h) are the work-in-progress source
>     code.
>      > Not finished yet.
>      >
>      > I have some questions.
>      >
>      > 1.
>      > In asynMotorController.h, the data type of motorPosition_
>      > and motorEncoderPosition_ are ints.
>      >
>      > But in derived class of asynMotorcontroller(like
>      > smarActMCSMotorDriver.cpp and ACRMotorDriver.cpp),
>      > the function setDoubleParam is used to set the above parameters.
>      >
>      > Why is that?
>      >
>      > 2.
>      > I can read the position(unit um, range 0 to 40) and voltage(unit
>     volts,
>      > range -30 to 130) from the controller.
>      > But which one should I set to motorPosition_and
>     motorEncoderPosition_?
>      > I don't quite understand.
>      >
>      > 3.
>      > The values of position and voltage from the controller are float
>     values.
>      > When setting, say 2.123456, to motorEncoderPosition, it will
>     become 2
>      > (which is an int).
>      >
>      > How to handle this situation?
>      > Should I do some kind of transformation? For example, multiply by
>     1000,000?
>      >
>      > 4.
>      > In the member function: asynStatus poll(bool* moving_p),
>      > I need to set the moving/done status of the stage to moving_p
>      > and motorStatusDone_.
>      >
>      > But there is no such exact suitable command provided by thhe
>     controller.
>      > A possible candidate is "get on-target status" meaning in closed-
>     loop
>      > mode(servo ON),
>      > whether the position(from sensor) approached the set target or not.
>      >
>      > 5.
>      > The controller has two modes:
>      > closed-loop mode(servo ON), users set the desired potion(unit um).
>      > open-loop mode(servo OFF), uses set the voltage(unit volts).
>      >
>      > What should I set to motorPosition_?
>      > I think it should be something like "motor pulse".
>      >
>      > what should I set to motorEncoderPosition_.?
>      > I think it should be something like "encoder value".
>      >
>      >
>      > ps
>      >
>      > The controller uses binary communication protocol.
>      > The following example is to read the position(in um) of channel 0:
>      > hexadecimal bytes in little-endian:
>      > 10 00 01 20 02 00 20 00 00 ac 01 00 00 00 00 fe
>      >
>      > 10 00: total length, 16 bytes
>      > 01 20: command ID, 0x2001 means "get position"
>      > 02 00: custom ID, used to distinguish different client programs.
>      > 20: option, 0x20 means "read"
>      > 00: sequence number, Used in very long responses. Not used in
>     commands.
>      > 00: interface id, RS-232 or USB or Ethernet. Not used in commands.
>      > ac: header checksum
>      > 01: data format, 01 means 32-bit unsigned int
>      > 00 00 00 00: data, channel 0
>      > fe: checksum
>      >
>      > The  response:
>      > 13 00 01 20 02 00 10 00 01 b8 00 00 02 70 74 6a 3e
>      >
>      > 13 00: total length, 19 bytes
>      > 01 20: command ID, 0x2001 means "get position"
>      > 02 00: custom ID, used to distinguish different client programs.
>      > 10: option, 0x10 means "final, no more".
>      > 00: sequence number,
>      > 01: interface id, 0x01 means RS-232.
>      > b8: header checksum
>      > 00: data format, 00 means 8-bit unsigned int
>      > 00: data, channel 0
>      > 02: data format, 02 means float
>      > 70 74 6a 3e: data, 0.228960 (position in um)
>      > 3e: checksum
>      >
>      >
>      > Any suggestions and responses are appreciated.
>      > Thank you.
>      >
>      >
>      > On Thu, Apr 24, 2025 at 5:31 AM Mark Rivers
>     <rivers at cars.uchicago.edu <mailto:rivers at cars.uchicago.edu>
>      > <mailto:rivers at cars.uchicago.edu
>     <mailto:rivers at cars.uchicago.edu>>> wrote:
>      >
>      >       * I think I would need to write the EPICS motor driver
>     module for
>      >         it.____
>      >
>      >     __ __
>      >
>      >     That is correct.  You should write a “Model 3” driver,
>     meaning it is
>      >     a C++ driver derived from the base classes
>     asynMotorController and
>      >     asynMotorAxis.____
>      >
>      >     __ __
>      >
>      >     I just looked at the manual for the nanoFAKTUR EBD-060310.
>     It uses
>      >     a non-standard binary communications protocol, not ASCII
>     strings.
>      >     That is unusual, most controllers use ASCII communication.____
>      >
>      >     __ __
>      >
>      >     The driver you suggested as a model, https://github.com/
>     epics-motor/ <https://github.com/epics-motor/>
>      >     motorPIGCS2 <https://urldefense.us/v3/__https:/github.com/
>     epics- <https://urldefense.us/v3/__https:/github.com/epics->
>      >     motor/motorPIGCS2__;!!G_uCfscf7eWS!
>      >   
>       YgABnMrr4ouWf_GcXAuVe7LU-96lfvWFNG96utdBA3mgcrk9dBsWVY7JQtxclFMQQYwD2Zz0RmrbQGad8OJXFPc$>  is fairly complex because of the class hierarchy, so you might want to start with something simpler as your example. ____
>      >
>      >     __ __
>      >
>      > https://github.com/epics-motor/motorSmarAct/blob/master/
>     smarActApp/ <https://github.com/epics-motor/motorSmarAct/blob/
>     master/smarActApp/>
>      >     src/smarActMCSMotorDriver.cpp <https://urldefense.us/v3/
>     __https:// <https://urldefense.us/v3/__https://>
>      > github.com/epics-motor/motorSmarAct/blob/master/smarActApp/src/
>     <http://github.com/epics-motor/motorSmarAct/blob/master/smarActApp/src/>
>      >     smarActMCSMotorDriver.cpp__;!!G_uCfscf7eWS!YLctRUSDiyz4K-
>      >   
>       t8_BzT33ew7q6EOnMR2oSaoEqf7fY5hpd5mII4cyq4pP6Gt3vwMPp1s38VSS7kXyY4shD2qrU$>____
>      >
>      >     __ __
>      >
>      >     That uses ASCII communication, but it should not be too hard
>     to take
>      >     the concepts and use binary communication.____
>      >
>      >     __ __
>      >
>      >     Mark____
>      >
>      >     __ __
>      >
>      >     __ __
>      >
>      >     __ __
>      >
>      >     __ __
>      >
>      >     __ __
>      >
>      >     *From:*Tech-talk <tech-talk-bounces at aps.anl.gov <mailto:tech-
>     talk-bounces at aps.anl.gov> <mailto:tech-talk- <mailto:tech-talk->
>      > bounces at aps.anl.gov <mailto:bounces at aps.anl.gov>>> *On Behalf Of
>     *LiangChih Chiang via Tech-talk
>      >     *Sent:* Wednesday, April 23, 2025 1:41 AM
>      >     *To:* tech-talk at aps.anl.gov <mailto:tech-talk at aps.anl.gov>
>     <mailto:tech-talk at aps.anl.gov <mailto:tech-talk at aps.anl.gov>>
>      >     *Subject:* Support for piezo controller nanoFAKTUR EBD-060310____
>      >
>      >     __ __
>      >
>      >     Hello, EPICS mates,____
>      >
>      >     __ __
>      >
>      >     I need to control a piezo sage with the controller nanoFAKTUR
>      >     EBD-060310.____
>      >
>      >     This is my first time using this controller.____
>      >
>      >     I searched and couldn't find the EPICS support module for it.____
>      >
>      >     __ __
>      >
>      >     I think I would need to write the EPICS motor driver module
>     for it.____
>      >
>      >     __ __
>      >
>      >     What module code would you recommend me to use as a starter
>      >     reference?____
>      >
>      >     For example, something like ____
>      >
>      >     EPICS motor drivers for Physik Instrumente GCS2 (General Command
>      >     Set) compatible controllers____
>      >
>      > https://github.com/epics-motor/motorPIGCS2 <https://github.com/
>     epics-motor/motorPIGCS2> <https://urldefense.us/ <https://
>     urldefense.us/>
>      >     v3/__https:/github.com/epics-motor/motorPIGCS2__;!!
>     G_uCfscf7eWS <http://github.com/epics-motor/motorPIGCS2__;!!
>     G_uCfscf7eWS>!
>      >   
>       YgABnMrr4ouWf_GcXAuVe7LU-96lfvWFNG96utdBA3mgcrk9dBsWVY7JQtxclFMQQYwD2Zz0RmrbQGad8OJXFPc$> ____
>      >
>      >     __ __
>      >
>      >     __ __
>      >
>

/* Motor driver support for nanoFAKTUR EBD-060310 Piezo Controller */

/* Derived from ACRMotorDriver.cpp by Mark Rivers, 2011/3/28 */
/* Derived from smarActMCSMotorDriver.cpp Till Straumann <strauman at slac.stanford.edu>, 9/11 */

/* Author: LiangChih Chiang <chiang.lc at nsrrc.org.tw>, 2026/01/21 */

#ifndef NANOFAKTUR_EBD_DRIVER_H
#define NANOFAKTUR_EBD_DRIVER_H

#ifdef __cplusplus

#include <stdarg.h>
#include <stdint.h>
#include <exception>

#include <asynMotorController.h>
#include <asynMotorAxis.h>

union ParamVal;
typedef union ParamVal ParamVal;


#define EXCEPTION_STR_LEN_MAX 100

enum NanoFakturEBDExceptionType {
	EBDUnknownError,
	EBDConnectionError,
	EBDCommunicationError,
};

class NanoFakturEBDException : public std::exception {
public:
	NanoFakturEBDException(NanoFakturEBDExceptionType t, const char *fmt, ...);
	NanoFakturEBDException(NanoFakturEBDExceptionType t)
		: t_(t)
		{ str_[0] = 0; }
	NanoFakturEBDException()
		: t_(EBDUnknownError)
		{ str_[0] = 0; }
	NanoFakturEBDException(NanoFakturEBDExceptionType t, const char *fmt, va_list ap);
    
	NanoFakturEBDExceptionType getType()
		const { return t_; }
	virtual const char *what()
		const throw() {return str_ ;}
        
protected:
	char str_[EXCEPTION_STR_LEN_MAX];
	NanoFakturEBDExceptionType t_;
};


class NanoFakturEBDAxis : public asynMotorAxis
{
public:
	NanoFakturEBDAxis(class NanoFakturEBDController *cnt_p, int axis, int channel);
    
	asynStatus poll(bool *moving_p);
	asynStatus move(double position, int relative, double minVel, double maxVel, double accel);
	asynStatus moveVelocity(double minVel, double maxVel, double accel);
	asynStatus stop(double acceleration);

    bool       getClosedLoop();
    bool       hasEncoder();

	asynStatus getUint(uint16_t cmdId, uint32_t *val_out);
	asynStatus getInt(uint16_t cmdId, int32_t *val_out);
	asynStatus getFloat(uint16_t cmdId, float *val_out);

	asynStatus setUint(uint16_t cmdId, uint32_t val);
	asynStatus setInt(uint16_t cmdId, int32_t val);
	asynStatus setFloat(uint16_t cmdId, float val);
    
    asynStatus setNone(uint16_t cmdId);
    
private:
	NanoFakturEBDController *c_p_;  // pointer to asynMotorController to which this axis belong 
	int        channel_;            // channel number used in commandsto controller; 0, 1, 2...
	asynStatus comStatus_;          // the last status of asyn communications

friend class NanoFakturEBDController;
};


class NanoFakturEBDController : public asynMotorController
{
public:
	NanoFakturEBDController(const char *portName, const char *IOPortName, int numAxes, double movingPollPeriod, double idlePollPeriod);
    
    asynStatus cmdRsp(uint16_t cmdId, int32_t isReadCmd, size_t params_count, const uint8_t *fmts, const ParamVal *vals, size_t *params_count_out, uint8_t *fmts_out, ParamVal *vals_out);
    
    asynStatus getVal(uint16_t cmdId, uint8_t fmt0, const void *val0, void *val_out);
    asynStatus getUint(uint16_t cmdId, uint32_t *val_out);
    asynStatus getInt(uint16_t cmdId, int32_t *val_out);
    asynStatus getFloat(uint16_t cmdId, float *val_out);

    asynStatus setVal(uint16_t cmdId, uint8_t fmt0, const void *val0, uint8_t fmt1, const void *val1);
    asynStatus setUint(uint16_t cmdId, uint32_t val);
    asynStatus setInt(uint16_t cmdId, int32_t val);
    asynStatus setFloat(uint16_t cmdId, float val);

protected:
	NanoFakturEBDAxis **pAxes_;  // pointer to array of axis pointer

private:
	asynUser *asynUserMot_p_;  // for asyn communications

friend class NanoFakturEBDAxis;
};

#endif // __cplusplus
#endif // NANOFAKTUR_EBD_DRIVER_H

Attachment: nanoFakturEBD01_motors.substitutions
Description: Binary data

Attachment: nanoFakturEBD01.doAfterIocInit
Description: Binary data

/* Motor driver support for nanoFAKTUR EBD-060310 Piezo Controller */

/* Derived from ACRMotorDriver.cpp by Mark Rivers, 2011/3/28 */
/* Derived from smarActMCSMotorDriver.cpp Till Straumann <strauman at slac.stanford.edu>, 9/11 */

/* Author: LiangChih Chiang <chiang.lc at nsrrc.org.tw>, 2026/01/21 */

#include <nanoFakturEBDDriver.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <iocsh.h>
#include <epicsString.h>
#include <epicsExport.h>
#include <asynOctetSyncIO.h>
#include <errlog.h>

/* Static configuration parameters (compile-time constants) */
// #undef  DEBUG
#define DEBUG

/* The asyn motor driver apparently can't cope with exceptions */
#undef  ASYN_CANDO_EXCEPTIONS
// #define ASYN_CANDO_EXCEPTIONS

/* Define this if exceptions should be thrown and it is OK to abort the application */
#undef  DO_THROW_EXCEPTIONS
// #define DO_THROW_EXCEPTIONS

#if defined(ASYN_CANDO_EXCEPTIONS) || defined(DO_THROW_EXCEPTIONS)
#define THROW_(e) throw e
#else
#define THROW_(e) epicsPrintf("%s\n", e.what());
#endif

#define DEFAULT_TIMEOUT 2.0
#define DEFAULT_TIMEOUT_SHORT 0.5

#define MOTOR_RES_R 1000000  // reverse of motor resolution; 1/MRES

#define TOLERANCE 0.001  // unit: um; used to determine moving or not

/* endian, byte order; start */

#define FLOAT_SIZE 4
#define DOUBLE_SIZE 8

// determine the endian of the platform
static int is_little_endian() {
    uint32_t test = 1;
    return *(uint8_t*)&test == 1;
}

// reverse byte order
static void reverse_bytes(uint8_t* bytes, size_t len) {
    size_t i;
    for (i = 0; i < len / 2; i++) {
        uint8_t temp = bytes[i];
        bytes[i] = bytes[len - 1 - i];
        bytes[len - 1 - i] = temp;
    }
}

// float(of the platform-endian) to little-endian 4-bytes
static void float_to_le_bytes(float value, uint8_t* le_bytes) {
    uint8_t temp[FLOAT_SIZE];
    
    memcpy(temp, &value, FLOAT_SIZE);
    
    if(!is_little_endian()){
        reverse_bytes(temp, FLOAT_SIZE);
    }
    
    memcpy(le_bytes, temp, FLOAT_SIZE);
}

// little-endian 4-bytes to float(of the platform-endian)
static float le_bytes_to_float(const uint8_t* le_bytes) {
    uint8_t temp[FLOAT_SIZE];
    float result;
    
    memcpy(temp, le_bytes, FLOAT_SIZE);  // copy to a temporary buffer
    
    if(!is_little_endian()){  // reverse byte order if it's big-endian
        reverse_bytes(temp, FLOAT_SIZE);
    }
    
    memcpy(&result, temp, FLOAT_SIZE);  // platform-endian
    
    return result;
}

// double(of the platform-endian) to little-endian 8-bytes
static void double_to_le_bytes(double value, uint8_t* le_bytes) {
    uint8_t temp[DOUBLE_SIZE];
    
    memcpy(temp, &value, DOUBLE_SIZE);
    
    if(!is_little_endian()){
        reverse_bytes(temp, DOUBLE_SIZE);
    }
    
    memcpy(le_bytes, temp, DOUBLE_SIZE);
}

// little-endian 8-bytes to double(of the platform-endian)
static double le_bytes_to_double(const uint8_t* le_bytes) {
    uint8_t temp[DOUBLE_SIZE];
    double result;
    
    memcpy(temp, le_bytes, DOUBLE_SIZE);
    
    if(!is_little_endian()){
        reverse_bytes(temp, DOUBLE_SIZE);
    }
    
    memcpy(&result, temp, DOUBLE_SIZE);
    
    return result;
}

/* endian, byte order; end */

/* nanaFaktur EBD; start */

// command id; consult nankFaktur EBD controller manual for details
#define CMD_CMD_LEVEL                0xFFF0

#define CMD_STOP_MOTION              0x2043

#define CMD_SERVO_ON                 0x2040

#define CMD_CURRENT_CTRL_TARGET      0x2015
#define CMD_CURRENT_CTRL_VOLT        0x2014
#define CMD_CURRENT_POS_ERROR        0x2013
#define CMD_CURRENT_TARGET           0x2012
#define CMD_OVERFLOW_STATUS          0x2011
#define CMD_ON_TARGET_STATUS         0x2010
#define CMD_OPEN_LOOP_TARGET_REL     0x2005
#define CMD_OPEN_LOOP_TARGET         0x2004
#define CMD_CLOSE_LOOP_TARGET_REL    0x2003
#define CMD_CLOSE_LOOP_TARGET        0x2002
#define CMD_GET_POS                  0x2001


static const char *cmd_id_to_str(uint16_t cmdId) {
    switch(cmdId){
        case CMD_CMD_LEVEL:              return "Command level";
        
        case CMD_STOP_MOTION:            return "Stop motion";
        
        case CMD_SERVO_ON:               return "Servo On";
        
        case CMD_CURRENT_CTRL_TARGET:    return "Currently controlling target";
        case CMD_CURRENT_CTRL_VOLT:      return "Currently controlling voltage";
        case CMD_CURRENT_POS_ERROR:      return "Currently position error";
        case CMD_CURRENT_TARGET:         return "Currently target ";
        case CMD_OVERFLOW_STATUS:        return "Overflow status";
        case CMD_ON_TARGET_STATUS:       return "On-target status";
        case CMD_OPEN_LOOP_TARGET_REL:   return "Open-loop target relative";
        case CMD_OPEN_LOOP_TARGET:       return "Open-loop target";
        case CMD_CLOSE_LOOP_TARGET_REL:  return "Close-loop target relative";
        case CMD_CLOSE_LOOP_TARGET:      return "Close-loop target";
        case CMD_GET_POS:                return "Get position";
        default:                         return "Unknown CmdId";
    }
}


// command and response use the same binary package definition; 
// byte order is little-endian;
// a package contains a header and zero-to-multiple parameters(data)
//
// uint16_t    len;          // total package size
// uint16_t    CmdId;        // command ID
// uint16_t    CustomId;     // value will be just returned (can be used for software-routinue)
// uint8_t     opt;          // option: e.g. controller should acknowledge, CRC or check-sum, etc.
// uint8_t     seq;          // sequence number: set by controller for response
// uint8_t     IntfId;       // interface ID: set by controller
// uint8_t     ChkSumHeader; // checksum for the header
// uint8_t     param0_fmt;   // parameters
//             param0_data   // size of data depends on the format
// uint8_t     param1_fmt;
//             param1_data
//             etc..
// uint8_t     ChkSumData;   // checksum for parameters

// from nF_common.h
#define MAX_PACKAGE_LEN                 1024 //!< maximal comamnd/response package size
#define MAX_CMDIN_PARAMETER_NUM          128 //!< maximal number of parameters in command package (to controller)
#define MAX_RSP_PARAMETER_NUM            512 //!< maximal number of parameters in response package (from controller)
#define MAX_PARAMETER_STRLEN              32 //!< maximal string length for controller's paramaters

// define lesser value which is ok if no need to process a response
// containing a lot of strings
#define MAX_PARAM_COUNT                    6 

// parameters union; modified from nF_interface.h
union ParamVal{ 
    double    dData;     // double
    float     fData;     // float
    uint32_t  uData;     // unsigned int
    int32_t   nData;     // signed int 
    char      sData[MAX_PARAMETER_STRLEN]; // space reserved for string
};
                            
// package header
// LSB(least significant byte), MSB(most significant byte)
// I(index)
#define HEADER_AND_CHECKSUM_SIZE (10)
#define HEADER_SIZE (HEADER_AND_CHECKSUM_SIZE - 1)

#define HEADER_START_I         (0)

#define HEADER_SIZE_LSB_I      (HEADER_START_I + 0)
#define HEADER_SIZE_MSB_I      (HEADER_START_I + 1)
#define HEADER_CMDID_LSB_I     (HEADER_START_I + 2)
#define HEADER_CMDID_MSB_I     (HEADER_START_I + 3)
#define HEADER_CUSTOMID_LSB_I  (HEADER_START_I + 4)
#define HEADER_CUSTOMID_MSB_I  (HEADER_START_I + 5)
#define HEADER_OPT_I           (HEADER_START_I + 6)
#define HEADER_SEQ_I           (HEADER_START_I + 7)
#define HEADER_INTFID_I        (HEADER_START_I + 8)
#define HEADER_CHECKSUM_I      (HEADER_START_I + HEADER_SIZE)

// package params(data)
// params_and_checksum_size = package_size - HEADER_AND_CHECKSUM_SIZE
// params_size = params_and_checksum_size - 1
#define PARAMS_START_I HEADER_AND_CHECKSUM_SIZE
// params_checksum_i = package_size - 1

// from nF_common.h; params(data) format
#define CMD_DATA_FMT_U8                 0   // unsigned char (Byte)
#define CMD_DATA_FMT_U32                1   // unsigned int (D-Word)
#define CMD_DATA_FMT_FLOAT              2   // float
#define CMD_DATA_FMT_DOUBLE             3   // double
#define CMD_DATA_FMT_STRING             4   // string
#define CMD_DATA_FMT_S32                5   // int
#define CMD_DATA_FMT_CHAR               6   // char
#define CMD_DATA_FMT_U16                7   // unsigned short (Word)
#define CMD_DATA_FMT_S16                8   // short
#define CMD_DATA_FMT_ARRAY              9   // array of data: (fmt_array, ArrSize, dataFmt, data[])
#define CMD_DATA_FMT_LF                10   // for host software: show a line-feed
#define CMD_DATA_FMT_INVALID         0xFF

typedef struct {
    uint16_t  len;
    uint16_t  cmdId; 
    uint16_t  customId;
    uint8_t   opt;    
    uint8_t   seq;
    uint8_t   intfId;
    uint16_t  checksumHeader;
    size_t    paramsCount;              // not in actual package
    uint8_t   fmts[MAX_PARAM_COUNT];    // parameter formats
    ParamVal  vals[MAX_PARAM_COUNT];    // parameter values
    uint16_t  checksumParams;
} nfPackageStruct;

// note: nFControl.exe uses 0x0001
//       Command Terminal in nFControl.exe uses 0x0002
#define CUSTOM_ID_EPICS     0x000E 

#define OPT_CMD_READ        0x20
#define OPT_CMD_WRITE       0x21

#define OPT_RSP_READ_MORE   0x90
#define OPT_RSP_READ_END    0x10

#define OPT_RSP_READ_ERROR  0x20

// ok or error
#define OPT_RSP_WRITE       0x21

// error code of response
// 0(zero) is ok; other values are errors
// as I know, error codes are all negative values.
#define RSP_ERR_OK  0

static const char *rsp_err_to_str(int32_t err) {
    switch(err){
        case RSP_ERR_OK: return "no error";
        case -1:  return "internal system error";
        case -2:  return "internal memory error";
        case -3:  return "internal function error";
        case -10: return "command package length error";
        case -11: return "command package check-sum error";
        case -12: return "command input timeout error";
        case -20: return "unknown command";
        case -21: return "command operation not allowed";
        case -22: return "currently command level too low";
        case -23: return "command input syntax error";
        case -24: return "command index out-of-range";
        case -25: return "command input-data-format error";  
        case -26: return "command input-data-value out-of-range";
        case -27: return "invalid number of input-data";
        case -28: return "command execution timeout";
        case -30: return "unknown parameter";            
        case -31: return "parameter operation not allowed";
        case -32: return "parameter needs higher command level";
        case -33: return "parameter input syntax error";
        case -34: return "parameter index out-of-range";
        case -35: return "parameter data-format error";
        case -36: return "parameter value out-of-range";
        case -40: return "wave parameter not valid";
        case -37: return "string length out-of-range"; 
        case -50: return "command password error";
        case -51: return "command/parameter is read-only";
        case -52: return "internal no enough memory error";
        case -70: return "macro not found";
        case -71: return "macro open() error";
        case -72: return "macro read() error";
        case -73: return "macro write() error";
        case -74: return "macro remove() error";
        case -75: return "macro is running";
        case -76: return "not allowed when receiving macro";
        default:  return "Unknown response error code";
    }
}

// error code when handling/parsing packages
typedef enum {
    PKG_ERR_OK              =  0,
    PKG_ERR_LEN             = -1,
    PKG_ERR_HEADER_CHECKSUM = -2,
    PKG_ERR_PARAMS_CHECKSUM = -3,
    PKG_ERR_PARAMS_TYPE     = -4,
} nfPkgErrCode;

static const char *pkg_err_to_str(nfPkgErrCode err) {
    switch(err){
        case PKG_ERR_OK:              return "Ok";
        case PKG_ERR_LEN:             return "Package length(size) error";
        case PKG_ERR_HEADER_CHECKSUM: return "Header checksum error";
        case PKG_ERR_PARAMS_CHECKSUM: return "Params checksum error";
        case PKG_ERR_PARAMS_TYPE:     return "Params data type error";
        default:                      return "Unknown error code";
    }
}

// calculate the checksum of package header and package params
static uint8_t checksum(const uint8_t *arr, size_t len){
    size_t i;
    uint8_t cks = 0;
    for(i = 0; i < len; i++){
        cks += arr[i];
    }
    cks = 0xFF - cks;
    return cks;
}

// parse package bytes into nfPackageStruct
static nfPkgErrCode parse_pkg(uint8_t *pkg, size_t pkg_size, nfPackageStruct *ps){   
    nfPkgErrCode err = PKG_ERR_OK;
    
    ps->len = pkg[HEADER_SIZE_LSB_I] | (pkg[HEADER_SIZE_MSB_I] << 8);
    if(ps->len != pkg_size){
#ifdef DEBUG
        printf("Error: package size check fail: in package %u, given size %lu\n", ps->len, pkg_size); 
#endif
        err = PKG_ERR_LEN;
        return err;
    }

    ps->cmdId    = pkg[HEADER_CMDID_LSB_I] | (pkg[HEADER_CMDID_MSB_I] << 8);
    ps->customId = pkg[HEADER_CUSTOMID_LSB_I] | (pkg[HEADER_CUSTOMID_MSB_I] << 8);
    ps->opt      = pkg[HEADER_OPT_I];
    ps->seq      = pkg[HEADER_SEQ_I];
    ps->intfId   = pkg[HEADER_INTFID_I];
    ps->checksumHeader = pkg[HEADER_CHECKSUM_I];
    
    const uint8_t checksumHeaderCalc = checksum(pkg, HEADER_SIZE);
    if(ps->checksumHeader != checksumHeaderCalc){
#ifdef DEBUG
        printf("Error: header checksum check fail: in package 0x%02x, calculated 0x%02x.\n", ps->checksumHeader, checksumHeaderCalc);
#endif
        err = PKG_ERR_HEADER_CHECKSUM;
        return err;
    }

    // parameters
    size_t i = PARAMS_START_I;  // package bytes index
    size_t j = 0;               // params count index
    const size_t params_checksum_i = pkg_size - 1;

    while(i < params_checksum_i){
        ps->fmts[j] = pkg[i];
        
        switch(pkg[i]){
            case CMD_DATA_FMT_U8:
                ps->vals[j].uData = pkg[i+1];
                i += (1+1);
            break;
            
            case CMD_DATA_FMT_U32:
                ps->vals[j].uData = pkg[i+1] | (pkg[i+2] << 8) | (pkg[i+3] << 16) | (pkg[i+4] << 24);
                i += (1+4);
            break;

            case CMD_DATA_FMT_FLOAT:
                memcpy(&ps->vals[j].fData, &pkg[i+1], FLOAT_SIZE);
                i += (1+FLOAT_SIZE);
            break;

            case CMD_DATA_FMT_DOUBLE: 
                memcpy(&ps->vals[j].dData, &pkg[i+1], DOUBLE_SIZE);
                i += (1+DOUBLE_SIZE);
            break;

            case CMD_DATA_FMT_STRING:
                strcpy(ps->vals[j].sData, (const char *)&pkg[i+1]);
                i += (1 + strlen((const char *)&pkg[i+1]) + 1);  // include NULL
            break;

            case CMD_DATA_FMT_S32:
                ps->vals[j].nData = pkg[i+1] | (pkg[i+2] << 8) | (pkg[i+3] << 16) | (pkg[i+4] << 24);
                i += (1+4);
            break;

            case CMD_DATA_FMT_CHAR:
                ps->vals[j].uData = pkg[i+1];
                i += (1+1);
            break;

            case CMD_DATA_FMT_U16:
                ps->vals[j].uData = pkg[i+1] | (pkg[i+2] << 8);
                i += (1+2);
            break;

            case CMD_DATA_FMT_S16:
                ps->vals[j].nData = pkg[i+1] | (pkg[i+2] << 8);
                i += (1+2);
            break;

            case CMD_DATA_FMT_ARRAY:  // can not handle; skip remaining params
                i = pkg_size;
                err = PKG_ERR_PARAMS_TYPE;
#ifdef DEBUG
                printf("Warning: un-handled param data format: 0x%02x(CMD_DATA_FMT_ARRAY).\n", CMD_DATA_FMT_ARRAY);
#endif
            break;

            case CMD_DATA_FMT_LF:
                i += 1;
            break;

            case CMD_DATA_FMT_INVALID:  // can not handle; skip remaining params
                i = pkg_size;
                err = PKG_ERR_PARAMS_TYPE;
#ifdef DEBUG
                printf("Warning: un-handled param data format: 0x%02x(CMD_DATA_FMT_INVALID).\n", CMD_DATA_FMT_INVALID);
#endif
            break;
            
            default:  // can not handle; skip remaining params
                i = pkg_size;
                err = PKG_ERR_PARAMS_TYPE;
#ifdef DEBUG
                printf("Warning: un-handled unknown param data format: 0x%02x(?).\n", pkg[i]);
#endif
            break;
        }
        
        j++;
    }   
    ps->paramsCount = j;

    if(ps->paramsCount > 0){  // has params
        const size_t params_and_checksum_size = pkg_size - HEADER_AND_CHECKSUM_SIZE;
        const size_t params_size = params_and_checksum_size - 1;

        ps->checksumParams = pkg[params_checksum_i];
        const uint8_t checksumParamsCalc = checksum(&pkg[PARAMS_START_I], params_size);
        if(ps->checksumParams != checksumParamsCalc){
#ifdef DEBUG
            printf("Error: params checksum check fail: in package 0x%02x, calculated 0x%02x.\n", ps->checksumParams, checksumParamsCalc);
#endif
            err = PKG_ERR_PARAMS_CHECKSUM;
            return err;
        }
    }

    return err;
}

// NF(nanoFaktur)
#define NF_IS_WRITE_CMD 1
#define NF_IS_READ_CMD 0

// make command package bytes
// return package size; package bytes is returned by argument pkg
static size_t make_pkg(uint16_t cmdId, int isReadCmd, size_t params_count, const uint8_t *fmts, const ParamVal *params, uint8_t *pkg){
    size_t i = PARAMS_START_I; // package bytes index
    size_t j;                  // params count index
    
    for(j = 0; j < params_count; j++){  // params
        switch(fmts[j]){
            case CMD_DATA_FMT_U8:
                pkg[i] = CMD_DATA_FMT_U8;
                pkg[i+1] = params[j].uData & 0xFF;
                i += (1+1);
            break;
            
            case CMD_DATA_FMT_U32:
                pkg[i] = CMD_DATA_FMT_U32;
                pkg[i+1] = params[j].uData & 0xFF;
                pkg[i+2] = (params[j].uData >> 8) & 0xFF;
                pkg[i+3] = (params[j].uData >> 16) & 0xFF;
                pkg[i+4] = (params[j].uData >> 24) & 0xFF;
                i += (1+4);
            break;

            case CMD_DATA_FMT_FLOAT:
                pkg[i] = CMD_DATA_FMT_FLOAT;
                float_to_le_bytes(params[j].fData, &pkg[i+1]);
                i += (1+FLOAT_SIZE);
            break;

            case CMD_DATA_FMT_DOUBLE:
                pkg[i] = CMD_DATA_FMT_DOUBLE;
                double_to_le_bytes(params[j].dData, &pkg[i+1]);
                i += (1+DOUBLE_SIZE);
            break;

            case CMD_DATA_FMT_STRING:
                pkg[i] = CMD_DATA_FMT_STRING;
                strcpy((char *)&pkg[i+1], params[j].sData);
                i += (1 + strlen((char *)&pkg[i+1]) + 1);
            break;

            case CMD_DATA_FMT_S32:
                pkg[i] = CMD_DATA_FMT_S32;
                pkg[i+1] = params[j].nData & 0xFF;
                pkg[i+2] = (params[j].nData >> 8) & 0xFF;
                pkg[i+3] = (params[j].nData >> 16) & 0xFF;
                pkg[i+4] = (params[j].nData >> 24) & 0xFF;
                i += (1+4);
            break;

            case CMD_DATA_FMT_CHAR:
                pkg[i] = CMD_DATA_FMT_CHAR;
                pkg[i+1] = params[j].uData & 0xFF;
                i += (1+1);
            break;

            case CMD_DATA_FMT_U16:
                pkg[i] = CMD_DATA_FMT_U16;
                pkg[i+1] = params[j].uData & 0xFF;
                pkg[i+2] = (params[j].uData >> 8) & 0xFF;
                i += (1+2);
            break;

            case CMD_DATA_FMT_S16:
                pkg[i] = CMD_DATA_FMT_S16;
                pkg[i+1] = params[j].nData & 0xFF;
                pkg[i+2] = (params[j].nData >> 8) & 0xFF;
                i += (1+2);
            break;

            case CMD_DATA_FMT_ARRAY:  // can not handle; skip remaining params
                j = params_count;
#ifdef DEBUG
                printf("Warning: un-handled param data format: 0x%02x(CMD_DATA_FMT_ARRAY).\n", CMD_DATA_FMT_ARRAY);
#endif
            break;

            case CMD_DATA_FMT_LF:
                pkg[i] = CMD_DATA_FMT_LF;
                i += (1);
            break;

            case CMD_DATA_FMT_INVALID:  // can not handle; skip remaining params
                j = params_count;
#ifdef DEBUG
                printf("Warning: un-handled param data format: 0x%02x(CMD_DATA_FMT_INVALID).\n", CMD_DATA_FMT_INVALID);
#endif
            break;
            
            default:  // can not handle; skip remaining params
                j = params_count;
#ifdef DEBUG
                printf("Warning: un-handled unknown param data format: 0x%02x(?).\n", fmts[j]);
#endif
            break;

        }
    }
    
    const uint16_t params_size = i - PARAMS_START_I;  // if 0, means no params
    const uint16_t params_checksum_size = params_size > 0 ? params_size + 1 : 0;
    const uint16_t pkg_size = HEADER_AND_CHECKSUM_SIZE + params_checksum_size;
    
    pkg[HEADER_SIZE_LSB_I] = pkg_size & 0xFF;
    pkg[HEADER_SIZE_MSB_I] = (pkg_size >> 8) & 0xFF;

    pkg[HEADER_CMDID_LSB_I] = cmdId & 0xFF;
    pkg[HEADER_CMDID_MSB_I] = (cmdId >> 8) & 0xFF;
    
    pkg[HEADER_CUSTOMID_LSB_I] = CUSTOM_ID_EPICS & 0xFF;
    pkg[HEADER_CUSTOMID_MSB_I] = (CUSTOM_ID_EPICS >> 8) & 0xFF;
    
    pkg[HEADER_OPT_I] = isReadCmd == NF_IS_READ_CMD ? OPT_CMD_READ : OPT_CMD_WRITE;
    pkg[HEADER_SEQ_I] = 0;
    pkg[HEADER_INTFID_I] = 0;

    pkg[HEADER_CHECKSUM_I] = checksum(pkg, HEADER_SIZE);  // header checksum

    if(params_size > 0){
        const uint16_t params_checksum_i = pkg_size - 1;
        pkg[params_checksum_i] = checksum(&pkg[PARAMS_START_I], params_size);  // params checksum
    }
    
    return pkg_size;
}

/* nanaFaktur EBD; end */

/* NanoFakturEBDException; start */ 

NanoFakturEBDException::NanoFakturEBDException(NanoFakturEBDExceptionType t, const char *fmt, ...)
	: t_(t)
{
    va_list ap;
	if ( fmt ) {
		va_start(ap, fmt);
		epicsVsnprintf(str_, sizeof(str_), fmt, ap);
		va_end(ap);
	} else {
		str_[0] = 0;
	}
};

NanoFakturEBDException::NanoFakturEBDException(NanoFakturEBDExceptionType t, const char *fmt, va_list ap)
    : t_(t)
{
	epicsVsnprintf(str_, sizeof(str_), fmt, ap);
}

/* NanoFakturEBDException; end */ 

/* NanoFakturEBDController; start */ 

NanoFakturEBDController::NanoFakturEBDController(const char *portName, const char *IOPortName, int numAxes, double movingPollPeriod, double idlePollPeriod)
	: asynMotorController(portName, // portName
                          numAxes,  // numAxes
	                      0,        // numParams
	                      0,        // interfaceMask
	                      0,        // interruptMask
	                      ASYN_CANBLOCK | ASYN_MULTIDEVICE, // asynFlags
	                      1,        // autoconnect
	                      0,        // priority
                          0)        // stack size
	, asynUserMot_p_(0)
{
    asynStatus       status;
    char             junk[100];
    size_t           got_junk;
    int              eomReason;
    pAxes_ = (NanoFakturEBDAxis **)(asynMotorController::pAxes_);

    /* asynStatus (*connect)(const char *port, int addr, asynUser **ppasynUser, const char *drvInfo); */
	status = pasynOctetSyncIO->connect(IOPortName, 0, &asynUserMot_p_, NULL);

	if(status){
		asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
		          "NanoFakturEBDController cannot connect to controller, asyn port name: %s\n", IOPortName);
		THROW_(NanoFakturEBDException(EBDConnectionError, "NanoFakturEBDController: unable to connect to controller"));
	}

	// slurp away any initial telnet negotiation; there is no guarantee that
	// the other end will not send some telnet chars in the future. The terminal
	// server should really be configured to 'raw' mode!
	status = pasynOctetSyncIO->read(asynUserMot_p_, junk, sizeof(junk), DEFAULT_TIMEOUT_SHORT, &got_junk, &eomReason);
	if(got_junk){
		asynPrint(this->pasynUserSelf, ASYN_TRACE_WARNING, "NanoFakturEBDController port %s: Warning: detected unexpected bytes on link (%s) initially.\n", portName, IOPortName);
	}

    /* set command level of the controller to 1; default is 0, not high enough */
    status = setUint(CMD_CMD_LEVEL, 1);
	if(status){
		asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
		          "NanoFakturEBDController port %s: Error: set command level failed. \n", portName);
	}

    // nanaFaktur EBD controllers use binary protocol, not ASCII protocol.
    // pasynOctetSyncIO->setInputEos(asynUserMot_p_, "\n", 1);
    // pasynOctetSyncIO->setOutputEos(asynUserMot_p_, "\n", 1);

	// Create axes; move to iocsh function nanoFakturEBDCreateAxis()
/*	for(ax = 0; ax <  numAxes; ax++){
		pAxes_[ax] = new NanoFakturEBDAxis(this, ax);
	}
*/

	startPoller(movingPollPeriod, idlePollPeriod, 2);
}


// read
//       command,  <no param>
//       command,  channel
//       response, value
//       response, channel, value, LF           
//       response, error code int32_t <0
//
// write
//       command, value
//       command, channel, value
//       response, ok, int32_t 0               Acknowledge
//       response, error code int32_t <0   
//
// examples:
//            read                     write
// 0x2001     u32 index                -                         get position
//            u8 index, float v, LF
// 0x2002     u32 index                u32 index, float v        close-loop target
//            u8 index, float v, LF    s32 0 ack
// 0x2003     -                        u32 index, float v        set close-loop target relative 
//                                     s32 0 ack
// 0x2010     u32 index                -                         get on-target status 
//            u8 index, u8 v(0,1), LF
// 0x2011     u32 index                -                         get overflow status 
//            u8 index, u8 v(0,1), LF
// 0x2040     u32 index                u32 index, u32 v(0,1)     servo controlling set/get 
//            u8 index, u8 v(0,1),LF   s32 0 ack
//
// 0x2043     -                        u32 index, (no value)     Stop motion 
//                                     s32 0 ack
//
// 0xFFF0     [level]                  u32 level                 command level
//            u32 value                s32 0 ack


// send command to controller and get response
asynStatus NanoFakturEBDController::cmdRsp(uint16_t cmdId, int32_t isReadCmd, size_t params_count, const uint8_t *fmts, const ParamVal *vals, size_t *params_count_out, uint8_t *fmts_out, ParamVal *vals_out)
{
    asynStatus status;
    uint8_t    pkg[MAX_PACKAGE_LEN];
    size_t     pkg_size;
    size_t     nwrite;
    int        eomReason;
    uint8_t    pkg_rsp[MAX_PACKAGE_LEN];
    size_t     pkg_rsp_size;
    nfPackageStruct ps_rsp;
    nfPkgErrCode pkg_rsp_err_code;
    size_t     j;

    pkg_size = make_pkg(cmdId, isReadCmd, params_count, fmts, vals, pkg);

/*
asynStatus (*writeRead)(asynUser   *pasynUser,
                        const char *write_buffer, size_t write_buffer_len,
                        char       *read_buffer,  size_t read_buffer_len,
                        double      timeout, 
                        size_t     *nbytesOut,    size_t *nbytesIn, 
                        int        *eomReason);
*/                
	status = pasynOctetSyncIO->writeRead(asynUserMot_p_, (const char *)pkg, pkg_size, 
                                         (char *)pkg_rsp, MAX_PACKAGE_LEN, 
                                         DEFAULT_TIMEOUT, &nwrite, &pkg_rsp_size, &eomReason);
    
    if(asynSuccess != status){
        asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
	              "Error: NanoFakturEBDController::cmdRsp pasynOctetSyncIO->writeRead, cmdId %04x, asynStatus %d\n",
                  cmdId, status);

        return status;
    }
    
    // here assumes that pkg_rsp contains the whole response; no need to read more

    pkg_rsp_err_code = parse_pkg(pkg_rsp, pkg_rsp_size, &ps_rsp);
    if(PKG_ERR_OK != pkg_rsp_err_code){
        asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
	              "Error: NanoFakturEBDController::cmdRsp response package error, cmdId %04x(%s), error code(%d): %s\n",
                  cmdId, cmd_id_to_str(cmdId), pkg_rsp_err_code, pkg_err_to_str(pkg_rsp_err_code));

        status = asynError;
        return status;
    }
    
    // check if error 
    if(ps_rsp.paramsCount == 1 && ps_rsp.fmts[0] == CMD_DATA_FMT_S32 && ps_rsp.vals[0].nData != 0){
        asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
                  "Error: NanoFakturEBDController::cmdRsp response error, cmdId 0x%04x(%s), error code(%d): %s\n",
                  cmdId, cmd_id_to_str(cmdId), ps_rsp.vals[0].nData, rsp_err_to_str(ps_rsp.vals[0].nData));
        status = asynError;
        return status;
    }

    *params_count_out = ps_rsp.paramsCount;
    for(j = 0; j < ps_rsp.paramsCount; j++){
        fmts_out[j] = ps_rsp.fmts[j];
        vals_out[j] = ps_rsp.vals[j];
    }
    
    return status;
}

// read command to get a value. 
// fmt0/val0 is ususally channel number; can be CMD_DATA_FMT_INVALID if no params
asynStatus NanoFakturEBDController::getVal(uint16_t cmdId, uint8_t fmt0, const void *val0, void *val_out)
{
    asynStatus status;
    size_t     params_count = 0;
    uint8_t    fmts[1];
    ParamVal   vals[1];
    size_t     params_count_out;
    uint8_t    fmts_out[3];  // channel, val_out, 
    ParamVal   vals_out[3];  // params returned from controller may contains an LF

    switch(fmt0){
        case CMD_DATA_FMT_U8:
        case CMD_DATA_FMT_U16:
        case CMD_DATA_FMT_U32:
            fmts[0] = fmt0;
            vals[0].uData = *(const uint32_t *)val0;
            params_count++;
        break;

        case CMD_DATA_FMT_CHAR:
        case CMD_DATA_FMT_S16:
        case CMD_DATA_FMT_S32:
            fmts[0] = fmt0;
            vals[0].nData = *(const int32_t *)val0;
            params_count++;
        break;

        case CMD_DATA_FMT_FLOAT:
        case CMD_DATA_FMT_DOUBLE:
            fmts[0] = fmt0;
            vals[0].fData = *(const float *)val0;
            params_count++;
        break;
        
        default:  // other formats are not handled
        break;
    }

    status = cmdRsp(cmdId, NF_IS_READ_CMD, params_count, fmts, vals, &params_count_out, fmts_out, vals_out);
    
    if(asynSuccess != status){
        asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
	              "Error: NanoFakturEBDController getVal cmdId %04x(%s), asynStatus %d\n", cmdId, cmd_id_to_str(cmdId), status);
        return status;
    }
    
    // assume if params_count_out is 1, response data is error code(s32 0 ack), or, value (e.g. cmdId 0xFFF0)
    //                               2,                  channel, value.
    //                               3                   channel, value, LF 
    size_t val_out_i = params_count_out == 1 ? 0 : 1;
    switch(fmts_out[val_out_i]){
        case CMD_DATA_FMT_U8:
        case CMD_DATA_FMT_U16:
        case CMD_DATA_FMT_U32:
            *(uint32_t *)val_out = vals_out[val_out_i].uData;
        break;

        case CMD_DATA_FMT_CHAR:
        case CMD_DATA_FMT_S16:
        case CMD_DATA_FMT_S32:
            *(int32_t *)val_out = vals_out[val_out_i].nData;
        break;

        case CMD_DATA_FMT_FLOAT:
        case CMD_DATA_FMT_DOUBLE:
            *(float *)val_out = vals_out[val_out_i].fData;
        break;

    }

    return status;
}

// read command; no params(no channel)
// example: 
asynStatus NanoFakturEBDController::getUint(uint16_t cmdId, uint32_t *val_out)
{
    return getVal(cmdId, CMD_DATA_FMT_INVALID, NULL, val_out);
}
asynStatus NanoFakturEBDController::getInt(uint16_t cmdId, int32_t *val_out)
{
    return getVal(cmdId, CMD_DATA_FMT_INVALID, NULL, val_out);
}
asynStatus NanoFakturEBDController::getFloat(uint16_t cmdId, float *val_out)
{
    return getVal(cmdId, CMD_DATA_FMT_INVALID, NULL, val_out);
}

// write command
// fmt0/val0 is ususally channel number or the value to set
// fmt1/val1 is usually the vale to set; can be CMD_DATA_FMT_INVALID if no params
// example: set closed-loop target of a channel
// example: set command level
asynStatus NanoFakturEBDController::setVal(uint16_t cmdId, uint8_t fmt0, const void *val0, uint8_t fmt1, const void *val1)
{
    asynStatus status;
    size_t     params_count = 0;
    uint8_t    fmts[2] = {fmt0, fmt1};        // magic number 2
    const void *p_vals[2] = {val0, val1};
    ParamVal   vals[2];
    size_t     params_count_out;
    size_t     j;
    uint8_t    fmts_out[2];       // index 0 for response error code, s32 0 is ok.
    ParamVal   vals_out[2];       // index 1 is for extra safety

    for(j = 0; j < 2; j++){
        switch(fmts[params_count]){
            case CMD_DATA_FMT_U8:
            case CMD_DATA_FMT_U16:
            case CMD_DATA_FMT_U32:
                vals[params_count].uData = *(const uint32_t *)p_vals[params_count];
                params_count++;
            break;

            case CMD_DATA_FMT_CHAR:
            case CMD_DATA_FMT_S16:
            case CMD_DATA_FMT_S32:
                vals[params_count].nData = *(const int32_t *)p_vals[params_count];
                params_count++;
            break;

            case CMD_DATA_FMT_FLOAT:
            case CMD_DATA_FMT_DOUBLE:
                vals[params_count].fData = *(const float *)p_vals[params_count];
                params_count++;
            break;
        }
    }

    status = cmdRsp(cmdId, NF_IS_WRITE_CMD, params_count, fmts, vals, &params_count_out, fmts_out, vals_out);

    if(asynSuccess != status){
        asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
	              "Error: NanoFakturEBDController setVal cmdId 0x%04x(%s), asynStatus %d\n", cmdId, cmd_id_to_str(cmdId), status);
        return status;
    }

    return status;
}

// write command; one param(value) to set
asynStatus NanoFakturEBDController::setUint(uint16_t cmdId, uint32_t val)
{
    asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
	          "NanoFakturEBDController setUint cmdId 0x%04x(%s), val %u\n", cmdId, cmd_id_to_str(cmdId), val);

    return setVal(cmdId, CMD_DATA_FMT_U32, &val, CMD_DATA_FMT_INVALID, NULL);
}
asynStatus NanoFakturEBDController::setInt(uint16_t cmdId, int32_t val)
{
    asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
	          "NanoFakturEBDController setInt cmdId 0x%04x(%s), val %d\n", cmdId, cmd_id_to_str(cmdId), val);

    return setVal(cmdId, CMD_DATA_FMT_S32, &val, CMD_DATA_FMT_INVALID, NULL);
}
asynStatus NanoFakturEBDController::setFloat(uint16_t cmdId, float val)
{
    asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
	          "NanoFakturEBDController setFloat cmdId 0x%04x(%s), val %f\n", cmdId, cmd_id_to_str(cmdId), val);

    return setVal(cmdId, CMD_DATA_FMT_FLOAT, &val, CMD_DATA_FMT_INVALID, NULL);
}

/* NanoFakturEBDController; end */ 

/* NanoFakturEBDAxis; start */ 

NanoFakturEBDAxis::NanoFakturEBDAxis(class NanoFakturEBDController *cnt_p, int axisNumber, int channel)
	: asynMotorAxis(cnt_p, axisNumber), c_p_(cnt_p)
{
	channel_ = channel;
    
	asynPrint(c_p_->pasynUserSelf, ASYN_TRACEIO_DRIVER, "NanoFakturEBDAxis creating axisNumber %d, channel %d\n", axisNumber, channel);

    setIntegerParam(c_p_->motorStatusHasEncoder_, 1);
    setIntegerParam(c_p_->motorStatusGainSupport_, 1);
    
    setIntegerParam(c_p_->motorPowerAutoOnOff_, 0);
    setDoubleParam(c_p_->motorPowerOnDelay_, 0);
    setDoubleParam(c_p_->motorPowerOffDelay_, 0);
    
    // setDoubleParam(c_p_->motorPostMoveDelay_, 0);

    // turn on servo; closed-loop
    if( (comStatus_ = setUint(CMD_SERVO_ON, 1)) ){
        asynPrint(c_p_->pasynUserSelf, ASYN_TRACE_ERROR,
                  "Error: NanoFakturEBDAxis channel %d, set Servo On failed\n", channel);

        setIntegerParam(c_p_->motorClosedLoop_, 0);
        goto bail;
    }
    setIntegerParam(c_p_->motorClosedLoop_, 1);

bail:
	setIntegerParam(c_p_->motorStatusProblem_, comStatus_ ? 1 : 0);
	setIntegerParam(c_p_->motorStatusCommsError_, comStatus_ ? 1 : 0);

	callParamCallbacks();  /* Do callbacks so higher layers see any changes */

	if(comStatus_){
		THROW_(NanoFakturEBDException(EBDCommunicationError, "NanoFakturEBDAxis::NanoFakturEBDAxis -- channel %u, ASYN error %i", axisNumber, comStatus_));
	}
}

/* Obtain value of the 'motorClosedLoop_' parameter (which maps to the record's CNEN field) */
bool NanoFakturEBDAxis::getClosedLoop()
{
    int val;
	c_p_->getIntegerParam(channel_, c_p_->motorClosedLoop_, &val);
	return val;
}

/* return 1 if encoder exists; return 0 otherwise */
bool NanoFakturEBDAxis::hasEncoder()
{
	int val;
	c_p_->getIntegerParam(channel_, c_p_->motorStatusHasEncoder_, &val);
	return val;
}

// get(read) a value for this channel from controller (nothing to do with asyn's parameter library).
// read command, parameter 0 is channel number
// cmdId:   controller command id
// val_out: where to store the value returned by the controller
// return:  asynError if an error occurred, asynSuccess otherwise.
asynStatus NanoFakturEBDAxis::getUint(uint16_t cmdId, uint32_t *val_out)
{
    return c_p_->getVal(cmdId, CMD_DATA_FMT_U32, &channel_, val_out);
}
asynStatus NanoFakturEBDAxis::getInt(uint16_t cmdId, int32_t *val_out)
{
    return c_p_->getVal(cmdId, CMD_DATA_FMT_U32, &channel_, val_out);
}
asynStatus NanoFakturEBDAxis::getFloat(uint16_t cmdId, float *val_out)
{
    return c_p_->getVal(cmdId, CMD_DATA_FMT_U32, &channel_, val_out);
}

// set(write) a value for this channel to controller
// write command, parameter 0 is channel number, parameter 1 is the value to set
asynStatus NanoFakturEBDAxis::setUint(uint16_t cmdId, uint32_t val)
{
    asynPrint(c_p_->pasynUserSelf, ASYN_TRACEIO_DRIVER,
	          "NanoFakturEBDAxis setUint cmdId 0x%04x(%s), val %u\n", cmdId, cmd_id_to_str(cmdId), val);
              
    return c_p_->setVal(cmdId, CMD_DATA_FMT_U32, &channel_, CMD_DATA_FMT_U32, &val);
}
asynStatus NanoFakturEBDAxis::setInt(uint16_t cmdId, int32_t val)
{
    asynPrint(c_p_->pasynUserSelf, ASYN_TRACEIO_DRIVER,
	          "NanoFakturEBDAxis setInt cmdId 0x%04x(%s), val %d\n", cmdId, cmd_id_to_str(cmdId), val);
              
    return c_p_->setVal(cmdId, CMD_DATA_FMT_U32, &channel_, CMD_DATA_FMT_S32, &val);
}
asynStatus NanoFakturEBDAxis::setFloat(uint16_t cmdId, float val)
{
     asynPrint(c_p_->pasynUserSelf, ASYN_TRACEIO_DRIVER,
	          "NanoFakturEBDAxis setFloat cmdId 0x%04x(%s), val %f\n", cmdId, cmd_id_to_str(cmdId), val);
              
   return c_p_->setVal(cmdId, CMD_DATA_FMT_U32, &channel_, CMD_DATA_FMT_FLOAT, &val);
}
// write command, parameter 0 is channel number, no parameter 1
// example: stop motion
asynStatus NanoFakturEBDAxis::setNone(uint16_t cmdId)
{
     asynPrint(c_p_->pasynUserSelf, ASYN_TRACEIO_DRIVER,
	          "NanoFakturEBDAxis setNone cmdId 0x%04x(%s)\n", cmdId, cmd_id_to_str(cmdId));
              
    return c_p_->setVal(cmdId, CMD_DATA_FMT_U32, &channel_, CMD_DATA_FMT_INVALID, NULL);
}

/** Poll the axis.
  * This function should read the controller position, encoder position, and as many of the motorStatus flags
  * as the hardware supports.  It should call setIntegerParam() and setDoubleParam() for each item that it polls,
  * and then call callParamCallbacks() at the end.
  * \param[out] moving A flag that the function must set indicating that the axis is moving (1) or done (0). */
asynStatus NanoFakturEBDAxis::poll(bool* moving_p)
{
	float    val_pos;
    float    val_target; // voltage or position
    uint32_t val_ontarget;
    uint32_t val_overflow;
    uint32_t val_servo_on;
    float    val_pos_error;
    uint32_t cmdId = getClosedLoop() ? CMD_CLOSE_LOOP_TARGET : CMD_OPEN_LOOP_TARGET;

// test; start
    double res = 0.000001;  // resolution
    c_p_->getDoubleParam(channel_, c_p_->motorResolution_, &res);
#ifdef DEBUG
	printf("----------- res %.10f\n", res);
#endif
// test; end

    if( (comStatus_ = getFloat(CMD_GET_POS, &val_pos)) ){
        goto bail;
    }
	setDoubleParam(c_p_->motorEncoderPosition_, val_pos * MOTOR_RES_R);  // magic number
    
    if( (comStatus_ = getFloat(cmdId, &val_target)) ){
        goto bail;
    }
	setDoubleParam(c_p_->motorPosition_, val_target * MOTOR_RES_R);  // magic number
    
    // Servo On status
    if( (comStatus_ = getUint(CMD_SERVO_ON, &val_servo_on)) ){
        goto bail;
    }

#ifdef DEBUG
	printf("NanoFakturEBDAxis poll, channel %d, servo_on %d, position %f, target %f", channel_, val_servo_on, val_pos, val_target);
#endif

    if((comStatus_ = getUint(CMD_ON_TARGET_STATUS, &val_ontarget))){
		goto bail;
    }

#ifdef DEBUG
	printf(", on-target status %u", val_ontarget);
#endif

    if((comStatus_ = getFloat(CMD_CURRENT_POS_ERROR, &val_pos_error))){
		goto bail;
    }
    
    *moving_p = 1;
    if(getClosedLoop() && (bool)val_ontarget){
        *moving_p = 0;
    }
    if(val_pos_error < TOLERANCE){  // magic number
        *moving_p = 0;
    }
	setIntegerParam(c_p_->motorStatusDone_, *moving_p ? 0 : 1);

#ifdef DEBUG
	printf(", *moving_p %u", *moving_p);
#endif

    if((comStatus_ = getUint(CMD_OVERFLOW_STATUS, &val_overflow))){
		goto bail;
    }
    setIntegerParam(c_p_->motorStatusProblem_, val_overflow ? 1 : 0);

#ifdef DEBUG
	printf(", overflow status %u", val_overflow);
#endif

bail:
	setIntegerParam(c_p_->motorStatusProblem_, comStatus_ ? 1 : 0);
	setIntegerParam(c_p_->motorStatusCommsError_, comStatus_ ? 1 : 0);

#ifdef DEBUG
	printf(".\n");
#endif

	callParamCallbacks();  /* Do callbacks so higher layers see any changes */

	return comStatus_;
}


// Move the motor to an absolute location or by a relative amount
// position, unit pulse(step)
// relative, relative move (1) or absolute move (0)
// minVelocity, initial velocity(base velocity), unit pulse/second
// maxVelocity, maximum velocity(slew velocity), unit pulse/second
// acceleration, unit pulse/sec/sec
asynStatus NanoFakturEBDAxis::move(double position, int relative, double minVel, double maxVel, double accel)
{
    uint32_t cmdId;
    
    if(getClosedLoop()){
        cmdId = relative ? CMD_CLOSE_LOOP_TARGET_REL : CMD_CLOSE_LOOP_TARGET;
    }
    else{
        cmdId = relative ? CMD_OPEN_LOOP_TARGET_REL : CMD_OPEN_LOOP_TARGET;
    }
    
#ifdef DEBUG
    printf("NanoFakturEBDAxis move(cmdId 0x%04x) channel %u, %s, %s to %f (speed %f - %f), accel %f\n", 
           cmdId, channel_, getClosedLoop() ? "ClosedLoop" : "OpenLoop", relative ? "relative" : "absolute", position, minVel, maxVel, accel);
#endif

    position /= MOTOR_RES_R;  // magic number
    if( (comStatus_ = setFloat(cmdId, position)) ){
        goto bail;
    }

bail:
	if(comStatus_){
		setIntegerParam(c_p_->motorStatusProblem_, 1);
		setIntegerParam(c_p_->motorStatusCommsError_, 1);
		callParamCallbacks();
	}
	return comStatus_;
}

// Move the motor at a fixed velocity until told to stop.
// jog
// No command we an use directly. Just move to limit.
asynStatus NanoFakturEBDAxis::moveVelocity(double minVel, double maxVel, double accel)
{
    double position;
    uint32_t cmdId = getClosedLoop() ? CMD_CLOSE_LOOP_TARGET : CMD_OPEN_LOOP_TARGET;
    
	if(maxVel > 0){
        c_p_->getDoubleParam(channel_, c_p_->motorHighLimit_, &position);
	}
    else{
        c_p_->getDoubleParam(channel_, c_p_->motorLowLimit_, &position);
    }

#ifdef DEBUG
	printf("NanoFakturEBDAxis moveVelocity(cmdId 0x%04x) channel %u, position %f, (speed %f - %f), accel %f\n", 
           cmdId, channel_, position, minVel, maxVel, accel);
#endif
    position /= MOTOR_RES_R;  // magic number
    if( (comStatus_ = setFloat(cmdId, position)) ){
        goto bail;
    }

bail:
	if(comStatus_){
		setIntegerParam(c_p_->motorStatusProblem_, 1);
		setIntegerParam(c_p_->motorStatusCommsError_, 1);
		callParamCallbacks();
	}
	return comStatus_;
}

// Stop the motor.
asynStatus NanoFakturEBDAxis::stop(double acceleration)
{
#ifdef DEBUG
	printf("NanoFakturEBDAxis stop channel %d\n", channel_);
#endif

    comStatus_ = setNone(CMD_STOP_MOTION);

	if ( comStatus_ ) {
		setIntegerParam(c_p_->motorStatusProblem_, 1);
		setIntegerParam(c_p_->motorStatusCommsError_, 1);
		callParamCallbacks();
	}
	return comStatus_;
}

/* NanoFakturEBDAxis; end */ 


/* iocsh wrapping and registration business; start */
/* (stolen from ACRMotorDriver.cpp) */

/* nanoFakturEBDCreateController called to create one nanoFaktur EBD controller */
static const iocshArg cc_a0 = {"Port name [string]",               iocshArgString};
static const iocshArg cc_a1 = {"I/O port name [string]",           iocshArgString};
static const iocshArg cc_a2 = {"Number of axes [int]",             iocshArgInt};
static const iocshArg cc_a3 = {"Moving poll period (ms) [double]", iocshArgDouble};
static const iocshArg cc_a4 = {"Idle poll period (ms) [double]",   iocshArgDouble};

static const iocshArg * const cc_as[] = {&cc_a0, &cc_a1, &cc_a2, &cc_a3, &cc_a4};

static const iocshFuncDef cc_def = {"nanoFakturEBDCreateController", sizeof(cc_as)/sizeof(cc_as[0]), cc_as};

extern "C" void *
nanoFakturEBDCreateController(
	const char *motorPortName,    // port name used in motor record
	const char *ioPortName,       // asyn port name
	int         numAxes,
	double      movingPollPeriod,
	double      idlePollPeriod)
{
    void *rval = 0;
    
	// the asyn stuff doesn't seem to be prepared for exceptions. I get segfaults
	// if constructing a controller (or axis) incurs an exception even if its
	// caught (IMHO asyn should behave as if the controller/axis never existed...)
#ifdef ASYN_CANDO_EXCEPTIONS
	try {
#endif
		rval = new NanoFakturEBDController(motorPortName, ioPortName, numAxes, movingPollPeriod/1000, idlePollPeriod/1000);  // create controller
#ifdef ASYN_CANDO_EXCEPTIONS
	} catch (NanoFakturEBDException &e) {
		epicsPrintf("nanoFakturEBDCreateController failed (exception caught):\n%s\n", e.what());
		rval = 0;
	}
#endif

	return rval;
}

static void cc_fn(const iocshArgBuf *args)
{
	nanoFakturEBDCreateController(
		args[0].sval,
		args[1].sval,
		args[2].ival,
		args[3].dval,
		args[4].dval);
}


/* nanoFakturEBDCreateAxis called to create each axis of a nanoFaktur EBD controller */
static const iocshArg ca_a0 = {"Controller Port name [string]",    iocshArgString};
static const iocshArg ca_a1 = {"Axis number [int]",                iocshArgInt};
static const iocshArg ca_a2 = {"Channel [int]",                    iocshArgInt};

static const iocshArg * const ca_as[] = {&ca_a0, &ca_a1, &ca_a2};

static const iocshFuncDef ca_def = {"nanoFakturEBDCreateAxis", sizeof(ca_as)/sizeof(ca_as[0]), ca_as};

extern "C" void *
nanoFakturEBDCreateAxis(
	const char *controllerPortName,
	int        axisNumber,  // from 0 to (numAxes-1); as index into asynMotorAxis **pAxes_ in asynMotorController
	int        channel)     // used in commands sent to the controller hardware 
{
    void *rval = 0;

    NanoFakturEBDController *pC;
    NanoFakturEBDAxis *pAxis;
    asynMotorAxis *pAsynAxis;

	// the asyn stuff doesn't seem to be prepared for exceptions. I get segfaults
	// if constructing a controller (or axis) incurs an exception even if its
	// caught (IMHO asyn should behave as if the controller/axis never existed...)
#ifdef ASYN_CANDO_EXCEPTIONS
	try {
#endif
		pC = (NanoFakturEBDController *) findAsynPortDriver(controllerPortName);
		if (!pC) {
			printf("nanoFakturEBDCreateAxis: Error port %s not found\n", controllerPortName);
			rval = 0;
			return rval;
		}
		
		pAsynAxis = pC->getAxis(axisNumber);
		if (pAsynAxis != NULL) { // check if axis number already exists
			epicsPrintf("nanoFakturEBDCreateAxis failed: axis %u already existed\n", axisNumber);
            
#ifdef ASYN_CANDO_EXCEPTIONS
			THROW_(NanoFakturEBDException(EBDCommunicationError, "axis %u already exists", axisNumber));
#endif
			rval = 0;
			return rval;
		}
		pC->lock();
		pAxis = new NanoFakturEBDAxis(pC, axisNumber, channel); // create axis
		pAxis = NULL;
		pC->unlock();

#ifdef ASYN_CANDO_EXCEPTIONS
	} catch (NanoFakturEBDException &e) {
		epicsPrintf("NanoFakturEBDAxis failed (exception caught):\n%s\n", e.what());
		rval = 0;
	}
#endif

	return rval;
}

static void ca_fn(const iocshArgBuf *args)
{
	nanoFakturEBDCreateAxis(
		args[0].sval,
		args[1].ival,
		args[2].ival);
}

static void nanoFakturEBDRegister(void)
{
  iocshRegister(&cc_def, cc_fn);  // CreateController
  iocshRegister(&ca_def, ca_fn);  // CreateAxis
}

extern "C" {
epicsExportRegistrar(nanoFakturEBDRegister);
}

/* iocsh wrapping and registration business; end */
## asyn port; nanoFaktur EBD piezo controller
## nanaFaktur EBD controller uses binary protocol, not ASCII protocol.
## drvAsynIPPortConfigure("portName", "hostInfo", priority, noAutoConnect, noProcessEos)
drvAsynIPPortConfigure("NFEBD01_PORT", "192.168.178.2:51000", 0, 0, 1)

## asynRecord

## Controller port, asyn port, number of axis, moving poll period(ms), idle poll period(ms)
## nanoFakturEBDCreateController(
##	const char *motorPortName,       # port
##	const char *ioPortName,          # asyn port
##	int         numAxes,             # number of axes
##	double      movingPollPeriod,    # unit: ms
##	double      idlePollPeriod)      # unit: ms
nanoFakturEBDCreateController("NFEBD01", "NFEBD01_PORT", 1, 100, 1000)

## portName, addr(-1 means all), mask
## /* traceMask definitions*/
##    #define ASYN_TRACE_ERROR     0x0001
##    #define ASYN_TRACEIO_DEVICE  0x0002
##    #define ASYN_TRACEIO_FILTER  0x0004
##    #define ASYN_TRACEIO_DRIVER  0x0008
##    #define ASYN_TRACE_FLOW      0x0010
##    #define ASYN_TRACE_WARNING   0x0020
## /* traceIO mask definitions*/
##    #define ASYN_TRACEIO_NODATA 0x0000
##    #define ASYN_TRACEIO_ASCII  0x0001
##    #define ASYN_TRACEIO_ESCAPE 0x0002
##    #define ASYN_TRACEIO_HEX    0x0004
asynSetTraceMask("NFEBD01", -1, 0x29)     # 0x21, 0x29, 
asynSetTraceIOMask("NFEBD01", -1, 0x02)   # 0x02, 0x04

## Controller port, axis number, controller channel
## nanoFakturEBDCreateAxis(
##	const char *controllerPortName,
##	int        axisNumber,
##	int        channel)
nanoFakturEBDCreateAxis("NFEBD01", 0, 0);

## Motors substitutions
dbLoadTemplate "live/nanoFakturEBD01_motors.substitutions"

## pseudo motors

appendToFile("$(BUILT_POSITIONS)", 'file live/nanoFakturEBD01_positions.req')
appendToFile("$(BUILT_SETTINGS)", 'file live/nanoFakturEBD01_settings.req')

< live/nanoFakturEBD01.doAfterIocInit

Replies:
Re: Support for piezo controller nanoFAKTUR EBD-060310 Torsten Bögershausen via Tech-talk
References:
Re: Support for piezo controller nanoFAKTUR EBD-060310 LiangChih Chiang via Tech-talk
Re: Support for piezo controller nanoFAKTUR EBD-060310 Torsten Bögershausen via Tech-talk
Re: Support for piezo controller nanoFAKTUR EBD-060310 LiangChih Chiang via Tech-talk
Re: Support for piezo controller nanoFAKTUR EBD-060310 Torsten Bögershausen via Tech-talk

Navigate by Date:
Prev: Re: How to send caPutLog logs to Grafana Loki Anders Lindh Olsson via Tech-talk
Next: Re: Support for piezo controller nanoFAKTUR EBD-060310 Torsten Bögershausen via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026
Navigate by Thread:
Prev: Re: Support for piezo controller nanoFAKTUR EBD-060310 Torsten Bögershausen via Tech-talk
Next: Re: Support for piezo controller nanoFAKTUR EBD-060310 Torsten Bögershausen via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  <2026
ANJ, 19 Mar 2026 · Home · News · About · Talk · Base · Modules · Extensions ·
· Distributions · Download · Documents · Links · Licensing ·