Logo Search packages:      
Sourcecode: hamlib version File versions  Download package

rotorez.c

/*
 * Hamlib backend library for the DCU rotor command set.
 *
 * rotorez.c - (C) Nate Bargmann 2003,2009,2010 (n0nb at arrl.net)
 *
 * This shared library provides an API for communicating
 * via serial interface to a Hy-Gain or Yaesu rotor using
 * the Idiom Press Rotor-EZ or RotorCard interface.  It also
 * supports the Hy-Gain DCU-1, and DF9GR ERC.
 *
 * Rotor-EZ is a trademark of Idiom Press
 * Hy-Gain is a trademark of MFJ Enterprises
 *
 * Tested on a HAM-IV with the Rotor-EZ V1.4S interface installed.
 *
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>                  /* Standard library definitions */
#include <string.h>                  /* String function definitions */
#include <unistd.h>                  /* UNIX standard function definitions */
#include <ctype.h>                    /* for isdigit function */

#include "hamlib/rotator.h"
#include "serial.h"
#include "misc.h"
#include "register.h"
#include "iofunc.h"

#include "rotorez.h"


/*
 * API local implementation
 *
 */

static int rotorez_rot_init(ROT *rot);
static int rotorez_rot_cleanup(ROT *rot);

static int rotorez_rot_set_position(ROT *rot, azimuth_t azimuth, elevation_t elevation);
static int rotorez_rot_get_position(ROT *rot, azimuth_t *azimuth, elevation_t *elevation);
static int erc_rot_get_position(ROT *rot, azimuth_t *azimuth, elevation_t *elevation);

static int rotorez_rot_reset(ROT *rot, rot_reset_t reset);
static int rotorez_rot_stop(ROT *rot);
static int dcu1_rot_stop(ROT *rot);

static int rotorez_rot_set_conf(ROT *rot, token_t token, const char *val);

static const char *rotorez_rot_get_info(ROT *rot);


/*
 * Private data structure
 */
00076 struct rotorez_rot_priv_data {
      azimuth_t az;
};

/*
 * Private helper function prototypes
 */

static int rotorez_send_priv_cmd(ROT *rot, const char *cmd);
static int rotorez_flush_buffer(ROT *rot);

/*
 * local configuration parameters
 */
static const struct confparams rotorez_cfg_params[] = {
      { TOK_ENDPT, "endpt", "Endpoint option", "Endpoint option",
            NULL, RIG_CONF_CHECKBUTTON, { }
      },
      { TOK_JAM, "jam", "Jam protection", "Jam protection",
            NULL, RIG_CONF_CHECKBUTTON, { }
      },
      { TOK_OVRSHT, "oversht", "Overshoot option", "Overshoot option",
            NULL, RIG_CONF_CHECKBUTTON, { }
      },
      { TOK_UNSTICK, "unstick", "Unstick option", "Unstick option",
            NULL, RIG_CONF_CHECKBUTTON, { }
      },
      { RIG_CONF_END, NULL, }
};

/* *************************************
 *
 * Separate model capabilities
 *
 * *************************************
 */


/*
 * Idiom Press Rotor-EZ enhanced rotor capabilities for
 * Hy-Gain CD45, Ham-II, Ham-III, Ham IV and all TailTwister rotors
 */

const struct rot_caps rotorez_rot_caps = {
      .rot_model =            ROT_MODEL_ROTOREZ,
      .model_name =           "Rotor-EZ",
      .mfg_name =             "Idiom Press",
      .version =              "2010-02-14",
      .copyright =            "LGPL",
      .status =               RIG_STATUS_BETA,
      .rot_type =             ROT_TYPE_OTHER,
      .port_type =            RIG_PORT_SERIAL,
      .serial_rate_min =      4800,
      .serial_rate_max =      4800,
      .serial_data_bits =     8,
      .serial_stop_bits =     1,
      .serial_parity =  RIG_PARITY_NONE,
      .serial_handshake =     RIG_HANDSHAKE_NONE,
      .write_delay =          0,
      .post_write_delay =     500,
      .timeout =              1500,
      .retry =                2,

      .min_az =               0,
      .max_az =               360,
      .min_el =               0,
      .max_el =               0,

      .priv =                       NULL, /* priv */
      .cfgparams =            rotorez_cfg_params,

      .rot_init =             rotorez_rot_init,
      .rot_cleanup =          rotorez_rot_cleanup,
      .set_position =         rotorez_rot_set_position,
      .get_position =         rotorez_rot_get_position,
      .stop =                       rotorez_rot_stop,
      .set_conf =             rotorez_rot_set_conf,
      .get_info =             rotorez_rot_get_info,

};


/*
 * Idiom Press RotorCard enhanced rotor capabilities for
 * Yaesu SDX and DXA series rotors
 */

const struct rot_caps rotorcard_rot_caps = {
      .rot_model =            ROT_MODEL_ROTORCARD,
      .model_name =           "RotorCard",
      .mfg_name =             "Idiom Press",
      .version =              "2010-02-14",
      .copyright =            "LGPL",
      .status =               RIG_STATUS_UNTESTED,
      .rot_type =             ROT_TYPE_OTHER,
      .port_type =            RIG_PORT_SERIAL,
      .serial_rate_min =      4800,
      .serial_rate_max =      4800,
      .serial_data_bits =     8,
      .serial_stop_bits =     1,
      .serial_parity =  RIG_PARITY_NONE,
      .serial_handshake =     RIG_HANDSHAKE_NONE,
      .write_delay =          0,
      .post_write_delay =     500,
      .timeout =              1500,
      .retry =                2,

      .min_az =               0,
      .max_az =               360,
      .min_el =               0,
      .max_el =               0,

      .priv =                       NULL, /* priv */
      .cfgparams =            rotorez_cfg_params,

      .rot_init =             rotorez_rot_init,
      .rot_cleanup =          rotorez_rot_cleanup,
      .set_position =         rotorez_rot_set_position,
      .get_position =         rotorez_rot_get_position,
      .stop =                       rotorez_rot_stop,
      .set_conf =             rotorez_rot_set_conf,
      .get_info =             rotorez_rot_get_info,

};


/*
 * Hy-Gain DCU-1/DCU-1X rotor capabilities
 */

const struct rot_caps dcu_rot_caps = {
      .rot_model =            ROT_MODEL_DCU,
      .model_name =           "DCU-1/DCU-1X",
      .mfg_name =             "Hy-Gain",
      .version =              "2010-08-23",
      .copyright =            "LGPL",
      .status =               RIG_STATUS_UNTESTED,
      .rot_type =             ROT_TYPE_OTHER,
      .port_type =            RIG_PORT_SERIAL,
      .serial_rate_min =      4800,
      .serial_rate_max =      4800,
      .serial_data_bits =     8,
      .serial_stop_bits =     1,
      .serial_parity =  RIG_PARITY_NONE,
      .serial_handshake =     RIG_HANDSHAKE_NONE,
      .write_delay =          0,
      .post_write_delay =     500,
      .timeout =              1500,
      .retry =                2,

      .min_az =               0,
      .max_az =               360,
      .min_el =               0,
      .max_el =               0,

      .priv =                       NULL, /* priv */

      .rot_init =             rotorez_rot_init,
      .rot_cleanup =          rotorez_rot_cleanup,
      .set_position =         rotorez_rot_set_position,
      .stop =                       dcu1_rot_stop,
      .reset =                rotorez_rot_reset,
      .get_info =             rotorez_rot_get_info,

};


/*
 * Rotor capabilities for DF9GR ERC
 *
 * TODO: Learn of additional capabilities of the ERC
 */

const struct rot_caps erc_rot_caps = {
      .rot_model =            ROT_MODEL_ERC,
      .model_name =           "ERC",
      .mfg_name =             "DF9GR",
      .version =              "2010-08-23b",          /* second revision on 23 Aug 2010 */
      .copyright =            "LGPL",
      .status =               RIG_STATUS_ALPHA,
      .rot_type =             ROT_TYPE_OTHER,
      .port_type =            RIG_PORT_SERIAL,
      .serial_rate_min =      4800,
      .serial_rate_max =      4800,
      .serial_data_bits =     8,
      .serial_stop_bits =     1,
      .serial_parity =  RIG_PARITY_NONE,
      .serial_handshake =     RIG_HANDSHAKE_NONE,
      .write_delay =          0,
      .post_write_delay =     500,
      .timeout =              1500,
      .retry =                2,

      .min_az =               0,
      .max_az =               360,
      .min_el =               0,
      .max_el =               0,

      .priv =                       NULL, /* priv */
//    .cfgparams =            rotorez_cfg_params,

      .rot_init =             rotorez_rot_init,
      .rot_cleanup =          rotorez_rot_cleanup,
      .set_position =         rotorez_rot_set_position,
      .get_position =         erc_rot_get_position,
      .stop =                       dcu1_rot_stop,
      .reset =                rotorez_rot_reset,
//    .stop =                       rotorez_rot_stop,
//    .set_conf =             rotorez_rot_set_conf,
      .get_info =             rotorez_rot_get_info,

};


/* ************************************
 *
 * API functions
 *
 * ************************************
 */


/*
 * Initialize data structures
 */

static int rotorez_rot_init(ROT *rot) {
      struct rotorez_rot_priv_data *priv;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      priv = (struct rotorez_rot_priv_data *)
      malloc(sizeof(struct rotorez_rot_priv_data));

      if (!priv)
            return -RIG_ENOMEM;
      rot->state.priv = (void *)priv;

      rot->state.rotport.type.rig = RIG_PORT_SERIAL;

      priv->az = 0;

      return RIG_OK;
}

/*
 * Clean up allocated memory structures
 */

static int rotorez_rot_cleanup(ROT *rot) {

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      if (rot->state.priv)
            free(rot->state.priv);
      rot->state.priv = NULL;

      return RIG_OK;
}


/*
 * Set position
 * DCU protocol supports azimuth only--elevation is ignored
 * Range is converted to an integer string, 0 to 360 degrees
 */

static int rotorez_rot_set_position(ROT *rot, azimuth_t azimuth, elevation_t elevation) {
      char cmdstr[8];
      char execstr[5] = "AM1;";
      int err;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      if (azimuth < 0 || azimuth > 360)
            return -RIG_EINVAL;

      if (azimuth > 359.4999)       /* DCU-1 compatibility */
            azimuth = 0;

      sprintf(cmdstr, "AP1%03.0f;", azimuth);         /* Target bearing */
      err = rotorez_send_priv_cmd(rot, cmdstr);
      if (err != RIG_OK)
            return err;

      err = rotorez_send_priv_cmd(rot, execstr);      /* Execute command */
      if (err != RIG_OK)
            return err;

      return RIG_OK;
}


/*
 * Get position
 * Returns current azimuth position in whole degrees.
 * Range returned from Rotor-EZ is an integer, 0 to 359 degrees
 * Elevation is set to 0
 */

static int rotorez_rot_get_position(ROT *rot, azimuth_t *azimuth, elevation_t *elevation) {
      struct rot_state *rs;
      char cmdstr[5] = "AI1;";
      char az[5];             /* read azimuth string */
      char *p;
      azimuth_t tmp = 0;
      int err;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      do {
            err = rotorez_send_priv_cmd(rot, cmdstr);
            if (err != RIG_OK)
                  return err;

            rs = &rot->state;

            err = read_block(&rs->rotport, az, AZ_READ_LEN);
            if (err != AZ_READ_LEN)
                  return -RIG_ETRUNC;

            /* The azimuth string should be ';xxx' beginning at offset 0.  If the
             * ';' is not there, it's likely the RotorEZ has received an invalid
             * command and the buffer needs to be flushed.  See
             * rotorez_flush_buffer() definition below for a complete description.
             */
            if (az[0] != ';') {
                  err = rotorez_flush_buffer(rot);
                  if (err == -RIG_EIO)
                        return err;
                  else
                        err = -RIG_EINVAL;
            } else if (err == AZ_READ_LEN && az[0] == ';') {
                  /* Check if remaining chars are digits if az[0] == ';' */
                  for (p = az + 1;p < az + 4; p++)
                        if (isdigit(*p))
                              continue;
                        else
                              err = -RIG_EINVAL;
            }
      } while (err == -RIG_EINVAL);

      /*
       * Rotor-EZ returns a four octet string consisting of a ';' followed
       * by three octets containing the rotor's position in degrees.  The
       * semi-colon is ignored when passing the string to atof().
       */
      az[4] = 0x00;                       /* NULL terminated string */
      p = az + 1;                         /* advance past leading ';' */
      tmp = (azimuth_t)atof(p);
      rig_debug(RIG_DEBUG_TRACE, "%s: \"%s\" after conversion = %.1f\n",
                        __func__, p, tmp);

      if (tmp == 360)
            tmp = 0;
      else if (tmp < 0 || tmp > 359)
            return -RIG_EINVAL;

      *azimuth = tmp;
      *elevation = 0;                     /* RotorEZ does not support elevation */
      rig_debug(RIG_DEBUG_TRACE,
                        "%s: azimuth = %.1f deg; elevation = %.1f deg\n",
                        __func__, *azimuth, *elevation);

      return RIG_OK;
}


/*
 * Get position for ERC
 * Returns current azimuth position in whole degrees.
 * Range returned from ERC is an integer, 0 to 359 degrees
 * Elevation is set to 0
 */

static int erc_rot_get_position(ROT *rot, azimuth_t *azimuth, elevation_t *elevation) {
      struct rot_state *rs;
      char cmdstr[5] = "AI1;";
      char az[5];             /* read azimuth string */
      char *p;
      azimuth_t tmp = 0;
      int err;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      do {
            err = rotorez_send_priv_cmd(rot, cmdstr);
            if (err != RIG_OK)
              return err;

            rs = &rot->state;

            err = read_block(&rs->rotport, az, AZ_READ_LEN);
            if (err != AZ_READ_LEN)
              return -RIG_ETRUNC;

            /* 
             * The azimuth string returned by the ERC should be 'xxx;'
             * beginning at offset 0.  A new version may implement the
             * Idiom PRess format, hence the test for ';xxx'.
             */

            /* Check if remaining chars are digits if az[3] == ';' */
            if (az[3] == ';') {
                  for (p = az; p < az + 3; p++)
                        if (isdigit(*p))
                              continue;
                        else
                              err = -RIG_EINVAL;
            } else if (az[0] == ';') {
                  /* Check if remaining chars are digits if az[0] == ';' */
                  for (p = az + 1;p < az + 4; p++)
                        if (isdigit(*p))
                              continue;
                        else
                              err = -RIG_EINVAL;
            }     
      } while (err == -RIG_EINVAL);

      /*
       * Old ERC returns a four octet string consisting of three octets 
       * followed by ';' ('xxx;') containing the rotor's position in degrees.
       * A new version will implement Idiom Press format of ';xxx'.
       *
       * We test for either case and pass a string of only digits to atof()
       */
      az[4] = 0x00;                 /* NULL terminated string */
      p = az;
      
      if (*p == ';')
            p++;
      else 
            az[3] = 0x00;           /* truncate trailing ';' */
      
      tmp = (azimuth_t)atof(p);
      rig_debug(RIG_DEBUG_TRACE, "%s: \"%s\" after conversion = %.1f\n",
                        __func__, p, tmp);

      if (tmp == 360)
            tmp = 0;
      else if (tmp < 0 || tmp > 359)
            return -RIG_EINVAL;

      *azimuth = tmp;
      *elevation = 0;                     /* ERC does not support elevation */
      rig_debug(RIG_DEBUG_TRACE,
                        "%s: azimuth = %.1f deg; elevation = %.1f deg\n",
                        __func__, *azimuth, *elevation);

      return RIG_OK;
}


/*
 * Stop rotation on RotorEZ, reset on DCU-1
 *
 * Sending the ";" string will stop rotation on the RotorEZ and reset the DCU-1
 */

static int rotorez_rot_stop(ROT *rot) {
      char cmdstr[2] = ";";
      int err;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      err = rotorez_send_priv_cmd(rot, cmdstr);
      if (err != RIG_OK)
            return err;

      return RIG_OK;
}

static int rotorez_rot_reset(ROT *rot, rot_reset_t reset) {
      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      return rotorez_rot_stop(rot);
}

/*
 * Stop rotation on DCU-1
 */

static int dcu1_rot_stop(ROT *rot) {
      char cmdstr[5] = "AS1;";
      int err;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      err = rotorez_send_priv_cmd(rot, cmdstr);
      if (err != RIG_OK)
            return err;

      return RIG_OK;
}


/*
 * Send configuration character
 *
 * Rotor-EZ interface will act on these commands immediately --
 * no other command or command terminator is needed.  Expects token
 * define in rotorez.h and *val of '1' or '0' (enable/disable).
 */

static int rotorez_rot_set_conf(ROT *rot, token_t token, const char *val) {
      char cmdstr[2];
      char c;
      int err;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
      rig_debug(RIG_DEBUG_TRACE, "%s: token = %d, *val = %c\n", __func__, token, *val);

      if (!rot)
            return -RIG_EINVAL;

      if (*val < '0' || *val > '1')
            return -RIG_EINVAL;

      switch(token) {
      case TOK_ENDPT:                /* Endpoint option */
            if (*val == '1')
                  c = 'E';
            else
                  c = 'e';
            break;
      case TOK_JAM:                    /* Jam protection */
            if (*val == '1')
                  c = 'J';
            else
                  c = 'j';
            break;
      case TOK_OVRSHT:              /* Overshoot option */
            if (*val == '1')
                  c = 'O';
            else
                  c = 'o';
            break;
      case TOK_UNSTICK:          /* Unstick option */
            if (*val == '1')
                  c = 'S';
            else
                  c = 's';
            break;
      default:
            return -RIG_EINVAL;
      }
      rig_debug(RIG_DEBUG_TRACE, "%s: c = %c, *val = %c\n", __func__, c, *val);
      snprintf(cmdstr, sizeof(cmdstr), "%c", c);

      rig_debug(RIG_DEBUG_TRACE, "%s: cmdstr = %s, *val = %c\n",
                    __func__, cmdstr, *val);

      err = rotorez_send_priv_cmd(rot, cmdstr);
      if (err != RIG_OK)
            return err;

      return RIG_OK;
}


/*
 * Get Info
 * returns the model name string
 */
static const char *rotorez_rot_get_info(ROT *rot)
{
      const struct rot_caps *rc;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return (const char *)-RIG_EINVAL;

      rc = rot->caps;

      return rc->model_name;
}


/*
 * Send command string to rotor
 */

static int rotorez_send_priv_cmd(ROT *rot, const char *cmdstr) {
      struct rot_state *rs;
      int err;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      rs = &rot->state;
      err = write_block(&rs->rotport, cmdstr, strlen(cmdstr));
      if (err != RIG_OK)
            return err;

      return RIG_OK;
}


/*
 * Flush the serial input buffer
 *
 * If the RotorEZ should receive an invalid command, such as an the ';'
 * character while the rotor is not in motion, as sent by the rotorez_rot_stop
 * function or the 'S' command from `rotctl', it will output the following
 * string, "C2000 IDIOM V1.4S " into the input buffer.  This function flushes
 * the buffer by reading it until a timeout occurs.  Once the timeout occurs,
 * this function returns and the buffer is presumed to be empty.
 */

static int rotorez_flush_buffer(ROT *rot) {
      struct rot_state *rs;
      char garbage[32];         /* read buffer */
      int err = 0;
      size_t MAX = 31;

      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      if (!rot)
            return -RIG_EINVAL;

      rs = &rot->state;
      do {
            err = read_block(&rs->rotport, garbage, MAX);

            /* Oops!  An IO error was encountered.  Bail out! */
            if (err == -RIG_EIO)
                  return -RIG_EIO;
      }
      while (err != -RIG_ETIMEOUT);

      return RIG_OK;
}


/*
 * Initialize backend
 */

DECLARE_INITROT_BACKEND(rotorez)
{
      rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

      rot_register(&rotorez_rot_caps);
      rot_register(&rotorcard_rot_caps);
      rot_register(&dcu_rot_caps);
      rot_register(&erc_rot_caps);

      return RIG_OK;
}


Generated by  Doxygen 1.6.0   Back to index