//
// svmlib.h
//

#ifndef _SVMLIB_H_
#define _SVMLIB_H_

#include <stdio.h>

// #ifdef __GNUC__
// #define _ATTR_PACKED_ __attribute__ ((packed))
// #else
// #define _ATTR_PACKED_
// #endif

#ifdef _MSC_VER // DllImport
#define DIMP extern "C" __declspec (dllimport)
#else
#define DIMP
#endif

#ifdef __GNUC__ // ifndef _MSC_VER
#include <stdint.h>
#else
typedef signed   char    int8_t;
typedef unsigned char   uint8_t;
typedef signed   short   int16_t;
typedef unsigned short  uint16_t;
typedef signed   int     int32_t;
typedef unsigned int    uint32_t;
#endif

#if defined(MINGW) || defined(_MSC_VER)
#include <windows.h>
#endif

/// PWM duty 100%
#define SVM_TESTDRV_MAX (16383)

/// servo mode constants
typedef enum {SVM_FREE=0, SVM_BRAKE, SVM_POSIT, 
	      SVM_VELOC, SVM_TESTDRV, SVM_MODENUM} svm_mode_t;

/// for calibration
#define SVM_DISABLED (0)
#define SVM_ENABLED  (1)
#define SVM_LOCKED   (2) // now automatically set SVM_VELOC...

#if defined(MINGW) || defined(_MSC_VER)
#define DEVFD_T HANDLE
#define DEVFD_ERROR (INVALID_HANDLE_VALUE)
#else
#define DEVFD_T int
#define DEVFD_ERROR (-1)
#endif

//
// Gear ratio       66
// Pulse/revolution 2000
//
#define SVM_WHEELED_BASE_COUNTSPEED_FROM_RPM(SPD) (SPD)*66/6000
#define SVM_WHEELED_BASE_RPM_FROM_COUNTSPEED(SPD) (SPD)*6000/66

/**
 *  @brief a RS485 interface device structure to a daisy chain of servo modules.
 *  This structure malloced at svm_open() and will be free at svm_close().
 */
typedef struct serif_st {
  /// RS485 device file descriptor
  DEVFD_T devfd;
  /// number of DOFs
  int dofnum;
  /// pointer to register-storage
  void *regs;
  /// log file
  FILE *log;
  /// log filename
  char *logname;
  /// pointer to an array of enabled/disabled state.
  char *enabled;
} serIF_t; // } _ATTR_PACKED_ serIF_t; 


/// servo module parameters
typedef struct {
  /// P gain (Position mode)
  uint16_t p_gain;
  /// D gain (Position mode)
  uint16_t d_gain;
  /// I gain (Position mode)
  uint16_t i_gain;
  /// P gain (Velocity mode)
  uint16_t vp_gain;
  /// D gain (Velocity mode)
  uint16_t vd_gain;
  /// I gain (Velocity mode)
  uint16_t vi_gain;
  /// velocity limit. encoder count per 1[ms].
  uint16_t veloc_limit;
  /// acceleration limit. encoder count per 1[ms].
  uint16_t accel_limit;
} svm_param_t; //} _ATTR_PACKED_ svm_param_t;


typedef struct {
  /// version, read-only. (effective after svm_read_config())
  uint8_t version;
  /// lower position limit of the encoder counter.
  int32_t lower_limit;
  /// upper position limit of the encoder counter.
  int32_t upper_limit;
  /// count direction of encoder counter.
  int8_t enc_dir;
  /// PWM drive direction.
  int8_t drive_dir;
  /// Calibration Offset
  int32_t calib_offset;
} svm_config_t;


typedef struct {
  uint8_t din;
  int16_t current;
  int16_t ain0;
  int16_t ain1;
  int16_t veloc;
  int32_t posit;
} svm_indata_t;


/**
 * @brief open the device and setup this library
 *
 * @param serIF    RS485 I/F to the servo-module chain (double pointer)
 * @param devname  serial device name
 * @param num      number of servo modules
 * @param baudrate baudrate of serial device, one of 2400, 4800, 9600, 
 *   19200, 38400, 57600, 115200, 230400, 460800, 576000, 921600, 1152000
 * @return 0 when suceeded, otherwise -1
 */
DIMP int svm_open(serIF_t **serIF, const char *devname, int num, int baudrate);

/**
 * @brief close the device after setting SVM_FREE mode.
 *
 * @param serIF RS485 I/F to the servo-module chain (double pointer)
 */
DIMP int svm_close(serIF_t **serIF);

/**
 * @brief send a ping and wait for a response.
 *
 * @param serIF RS485 I/F to the servo-module chain
 * @param svmid servo module ID
 */
DIMP int svm_ping(serIF_t *serIF, int svmid);

/**
 *  @brief disable a servo module.
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 */
DIMP int svm_disable_module(serIF_t *serIF, int svmid);

/**
 *  @brief lock a servo module (SVM_VELOC mode).
 *
 *  the module's mode changes to SVM_VELOC mode and always 'zero speed' target.
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 */
DIMP int svm_lock_module(serIF_t *serIF, int svmid);

/**
 *  @brief unlock a servo module. 
 *
 *  the module's mode changes to specified mode.
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 *  @param svmode one of SVM_FREE, SVM_BRAKE, SVM_POSIT, SVM_VELOC, SVM_TESTDRV
 */
DIMP int svm_unlock_module(serIF_t *serIF, int svmid, const svm_mode_t svmode);

/**
 *  @brief enable a servo module. 
 *
 *  When enables a disabled module, its mode changes to FREE mode.
 *  When enables a enabled module, its mode doesn't change.
 *
 *  @param serIF   RS485 I/F to the servo-module chain
 *  @param svmid   servo module ID
 *  @param timeout timeout in miliseconds
 */
DIMP int svm_enable_module(serIF_t *serIF, int svmid, int timeout);

/**
 *  @brief send servo paramters of all modules. Disabled modules will be ignored.
 *
 * @param serIF RS485 I/F to the servo-module chain
 * @param param servo parameters of all modules
 */
DIMP int svm_set_param(serIF_t *serIF, const svm_param_t param[]);

/**
 *  @brief send servo configrations of all modules. Disabled modules will be ignored.
 *
 * @param serIF RS485 I/F to the servo-module chain
 * @param param servo parameters of all modules
 */
DIMP int svm_set_config(serIF_t *serIF, const svm_config_t config[]);

/**
 *  @brief set servo mode of all modules.
 *
 *  the mode of locked module remains SVM_VELOC mode automatically.
 *
 *  @param serIF  RS485 I/F to the servo-module chain
 *  @param svmode one of SVM_FREE, SVM_BRAKE, SVM_POSIT, SVM_VELOC, SVM_TESTDRV
 */
DIMP int svm_set_mode(serIF_t *serIF, const svm_mode_t svmode);

/**
 *  @brief get current positions of all modules.
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param posit[] current position in encoder count (return value)
 */
DIMP int svm_get_posit(serIF_t *serIF, int32_t posit[]);

/**
 *  @brief  send position targets of all modules.
 *
 *  @param serIF   RS485 I/F to the servo-module chain
 *  @param posit[] target positions in encoder count
 *  @return 0 if succeeded, otherwise -1.
 */
DIMP int svm_set_posit(serIF_t *serIF, const int32_t posit[]);

/**
 *  @brief get velocity targets of all modules.
 *
 *  @param serIF   RS485 I/F to the servo-module chain
 *  @param veloc[] current velocities (encoder count per 10[ms])
 */
DIMP int svm_get_veloc(serIF_t *serIF, int16_t veloc[]);

/**
 *  @brief send velocity targets of all modules.
 *
 *  the velocity target of locked module is overwritten by 0 automatically.
 *
 *  @param serIF   RS485 I/F to the servo-module chain
 *  @param veloc[] target velocities (encoder count per 10[ms])
 */
DIMP int svm_set_veloc(serIF_t *serIF, int16_t veloc[]);

/**
 *  @brief get DINx/AINx data of a module.
 *
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 *  @param data  data (return value)
 */
DIMP int svm_get_indata(serIF_t *serIF, int svmid, svm_indata_t *data);

/**
 *  @brief send dout data and directions of DIO ports of a module.
 *
 *  @param serIF  RS485 I/F to the servo-module chain
 *  @param svmid  servo module ID
 *  @param dir    bitmap of I/O direction. 0 for input, 1 for output.
 *  @param data   bitmap of I/O data
 */
DIMP int svm_set_dout(serIF_t *serIF, int svmid, uint8_t dir, uint8_t data);


/**
 *  @brief send pwm duties of a module for test drive mode (SVM_TESTDRV).
 *
 *  @param serIF   RS485 I/F to the servo-module chain
 *  @param drive[] target duty
 * <table>
 * <tr><td> 0x8000-0xBFFF </td><td> BRAKE </td></tr>
 * <tr><td> 0xC000-0xFFFF </td><td> CCW   </td></tr>
 * <tr><td> 0x0000-0x3FFF </td><td> CW    </td></tr>
 * <tr><td> 0x4000-0x7FFF </td><td> FREE  </td></tr>
 * </table>
 */
DIMP int svm_set_testdrv(serIF_t *serIF, const int drive[]);

/**
 *  @brief change the servo module ID No.
 *
 *  @param serIF   RS485 I/F to the servo-module chain
 *  @param fromid  original module ID
 *  @param toid    new module ID
 */
DIMP int svm_change_id(serIF_t *serIF, int fromid, int toid);

/**
 *  @brief change buadrate of a servo module.
 *
 *  @param serIF  RS485 I/F to the servo-module chain
 *  @param svmid  servo module ID
 *  @param baudrate one of 2400, 4800, 9600, 19200, 38400, 57600, 115200,
 *                         230400, 460800, 576000, 921600, 1152000
 */
DIMP int svm_change_baudrate(serIF_t *serIF, int svmid, int baudrate);

/**
 *  @brief program parameters into Flash ROM storage area on a module.
 *
 *  @param serIF  RS485 I/F to the servo-module chain
 *  @param svmid  servo module ID
 */
DIMP int svm_program(serIF_t *serIF, int svmid);

/**
 *  @brief read a parameters from a module.
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 *  @param param paramters (return value)
 */
DIMP int svm_read_param(serIF_t *serIF, int svmid, svm_param_t *param);

/**
 *  @brief write parameters to a servo module
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 *  @param param servo parameters
 *
 */
DIMP int svm_write_param(serIF_t *serIF, int svmid, const svm_param_t *param);

/**
 *  @brief read a configuration from a module.
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 *  @param param paramters (return value)
 */
DIMP int svm_read_config(serIF_t *serIF, int svmid, svm_config_t *config);

/**
 *  @brief write a configuration to a servo module
 *
 *  @param serIF RS485 I/F to the servo-module chain
 *  @param svmid servo module ID
 *  @param param servo parameters
 *
 */
DIMP int svm_write_config(serIF_t *serIF, int svmid, 
			  const svm_config_t *config);

/**
 *  @brief open the device (not setup of registers, but of log file.)
 *
 *  @param serIF RS485 I/F to the servo-module chain (double pointer)
 *  @param devname  serial device name
 *  @param baudrate one of 2400, 4800, 9600, 19200, 38400, 57600, 115200,
 *                         230400, 460800, 576000, 921600, 1152000
 *  @return device file descriptor
 */
DIMP int svm_open_device(serIF_t **serIF, const char *devname, int baudrate);

/**
 *  @brief close the device (without any resetting)
 *
 *  @param serIF RS485 I/F to the servo-module chain (double pointer)
 */
DIMP int svm_close_device(serIF_t **serIF);

#endif // _SVMLIB_H_
