From 4776b181f60821777b7995345b9c04480feae83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Fillod=2C=20F8CFE?= Date: Mon, 1 Jun 2009 10:28:55 +0000 Subject: [PATCH] Support for the SPID Rot2Prog rotator controller, by Norvald H. Ryeng, LA6YKA git-svn-id: https://hamlib.svn.sourceforge.net/svnroot/hamlib/trunk@2699 7ae35d74-ebe9-4afe-98af-79ac388436b8 --- AUTHORS | 4 + Makefile.am | 2 +- configure.ac | 3 +- include/hamlib/rotlist.h | 11 ++ spid/Makefile.am | 7 + spid/spid.c | 294 +++++++++++++++++++++++++++++++++++++++ spid/spid.h | 28 ++++ spid/spid.txt | 208 +++++++++++++++++++++++++++ 8 files changed, 555 insertions(+), 2 deletions(-) create mode 100644 spid/Makefile.am create mode 100644 spid/spid.c create mode 100644 spid/spid.h create mode 100644 spid/spid.txt diff --git a/AUTHORS b/AUTHORS index 0ff7b5994..306a66db8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -192,6 +192,9 @@ M: Rob Frohne, KL7NA [rotorez] M: Nate Bargmann, N0NB +[spid] +M: Norvald H. Ryeng, LA6YKA + [Frontend] M: Stephane Fillod, F8CFE @@ -260,3 +263,4 @@ Steve Conklin, AI4QR, Martin Ewing, AA6E, Terry Embry, KJ4EED, Alessandro Zummo, IZ1PRB +Norvald H. Ryeng, LA6YKA diff --git a/Makefile.am b/Makefile.am index 513f87c66..abcb46a49 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ DIST_SUBDIRS = macros include lib libltdl src c++ bindings tests doc \ icom kenwood aor yaesu dummy pcr alinco uniden tentec kachina jrc \ rpcrig winradio easycomm fodtrack rpcrot drake rotorez \ flexradio sartek lowe rft rs tapr kit skanti wj racal tuner \ - gs232a heathkit + gs232a heathkit spid rpm: Makefile make dist diff --git a/configure.ac b/configure.ac index a6c4151c6..a8d058bbb 100644 --- a/configure.ac +++ b/configure.ac @@ -229,7 +229,7 @@ AC_SUBST(RIGMATRIX) BACKEND_LIST="icom kenwood aor yaesu dummy pcr alinco uniden tentec kachina jrc drake lowe rft rs kit skanti tapr flexradio wj racal tuner" -ROT_BACKEND_LIST="dummy easycomm fodtrack gs232a heathkit kit rotorez sartek" +ROT_BACKEND_LIST="dummy easycomm fodtrack gs232a heathkit kit rotorez sartek spid" BINDINGS="" BINDING_ALL="" BINDING_CHECK="" @@ -496,6 +496,7 @@ easycomm/Makefile fodtrack/Makefile gs232a/Makefile heathkit/Makefile +spid/Makefile sartek/Makefile rpcrig/Makefile rpcrot/Makefile diff --git a/include/hamlib/rotlist.h b/include/hamlib/rotlist.h index e89f8a289..d7bd23aca 100644 --- a/include/hamlib/rotlist.h +++ b/include/hamlib/rotlist.h @@ -166,6 +166,16 @@ #define ROT_BACKEND_HEATHKIT "heathkit" #define ROT_MODEL_HD1780 ROT_MAKE_MODEL(ROT_HEATHKIT, 1) +/*! \def ROT_MODEL_SPID + * \brief A macro that returns the model number of the SPID backend. + * + * The SPID backend can be used with rotators that support the SPID + * protocol. + */ +#define ROT_SPID 9 +#define ROT_BACKEND_SPID "spid" +#define ROT_MODEL_SPID ROT_MAKE_MODEL(ROT_SPID, 1) + /*! \typedef typedef int rot_model_t \brief Convenience type definition for rotator model. */ @@ -188,6 +198,7 @@ typedef int rot_model_t; { ROT_GS232A, ROT_BACKEND_GS232A }, \ { ROT_KIT, ROT_BACKEND_KIT }, \ { ROT_HEATHKIT, ROT_BACKEND_HEATHKIT }, \ + { ROT_SPID, ROT_BACKEND_SPID }, \ { 0, NULL }, /* end */ \ } diff --git a/spid/Makefile.am b/spid/Makefile.am new file mode 100644 index 000000000..3b5840d2a --- /dev/null +++ b/spid/Makefile.am @@ -0,0 +1,7 @@ + +lib_LTLIBRARIES = hamlib-spid.la +hamlib_spid_la_SOURCES = spid.c +hamlib_spid_la_LDFLAGS = -no-undefined -module -avoid-version +hamlib_spid_la_LIBADD = $(top_builddir)/src/libhamlib.la + +noinst_HEADERS = spid.h diff --git a/spid/spid.c b/spid/spid.c new file mode 100644 index 000000000..7d55bc9af --- /dev/null +++ b/spid/spid.c @@ -0,0 +1,294 @@ +/* + * Hamlib Rotator backend - SPID Rot2Prog + * Copyright (c) 2009 by Norvald H. Ryeng, LA6YKA + * + * $Id$ + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "hamlib/rotator.h" +#include "serial.h" +#include "misc.h" +#include "register.h" + +#include "spid.h" + +#define TOK_AZRES 1 +#define TOK_ELRES 2 + +struct spid_priv_data { + int az_resolution; + int el_resolution; +}; + +static int spid_rot_init(ROT *rot) +{ + struct spid_priv_data *priv; + + rig_debug(RIG_DEBUG_TRACE, "%s called\n", __FUNCTION__); + + if (!rot || !rot->caps) + return -RIG_EINVAL; + + priv = (struct spid_priv_data*)malloc(sizeof(struct spid_priv_data)); + if (!priv) { + return -RIG_ENOMEM; + } + + rot->state.priv = (void*)priv; + + priv->az_resolution = 0; + priv->el_resolution = 0; + + return RIG_OK; +} + +static int spid_rot_cleanup(ROT *rot) +{ + rig_debug(RIG_DEBUG_TRACE, "%s called\n", __FUNCTION__); + + if (!rot) + return -RIG_EINVAL; + + if (rot->state.priv) + free(rot->state.priv); + rot->state.priv = NULL; + + return RIG_OK; +} + +static int spid_get_conf(ROT *rot, token_t token, char *val) +{ + struct spid_priv_data *priv = (struct spid_priv_data*)rot->state.priv; + + rig_debug(RIG_DEBUG_TRACE, "%s called %d\n", __FUNCTION__, token); + + switch(token) { + case TOK_AZRES: + sprintf(val, "%d", priv->az_resolution); + break; + case TOK_ELRES: + sprintf(val, "%d", priv->el_resolution); + break; + default: + return -RIG_EINVAL; + } + return RIG_OK; +} + +static int spid_set_conf(ROT *rot, token_t token, const char *val) +{ + struct spid_priv_data *priv = (struct spid_priv_data*)rot->state.priv; + + rig_debug(RIG_DEBUG_TRACE, "%s called %d %s\n", __FUNCTION__, token, val); + + switch(token) { + case TOK_AZRES: + priv->az_resolution = atoi(val); + break; + case TOK_ELRES: + priv->el_resolution = atoi(val); + break; + default: + return -RIG_EINVAL; + } + return RIG_OK; +} + +static int spid_rot_set_position(ROT *rot, azimuth_t az, elevation_t el) +{ + struct rot_state *rs; + rs = &rot->state; + struct spid_priv_data *priv = (struct spid_priv_data*)rs->priv; + int retval; + int retry_read = 0; + char cmdstr[13]; + unsigned int u_az, u_el; + + rig_debug(RIG_DEBUG_TRACE, "%s called: %f %f\n", __FUNCTION__, az, el); + + if (!priv->az_resolution || !priv->el_resolution) { + do { + retval = write_block(&rs->rotport, "\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1F\x20", 13); + if (retval != RIG_OK) { + return retval; + } + + memset(cmdstr, 0, 12); + retval = read_block(&rs->rotport, cmdstr, 12); + } while (retval < 0 && retry_read++ < rot->state.rotport.retry); + if (retval < 0) + return retval; + } else { + cmdstr[5] = priv->az_resolution; /* PH */ + cmdstr[10] = priv->el_resolution; /* PV */ + } + + u_az = cmdstr[5]*(360 + az); + u_el = cmdstr[10]*(360 + el); + + cmdstr[0] = 0x57; /* S */ + cmdstr[1] = 0x30 + u_az/1000; /* H1 */ + cmdstr[2] = 0x30 + (u_az % 1000) / 100; /* H2 */ + cmdstr[3] = 0x30 + (u_az % 100) / 10; /* H3 */ + cmdstr[4] = 0x30 + (u_az % 10); /* H4 */ + + cmdstr[6] = 0x30 + u_el / 1000; /* V1 */ + cmdstr[7] = 0x30 + (u_el % 1000) / 100; /* V2 */ + cmdstr[8] = 0x30 + (u_el % 100) / 10; /* V3 */ + cmdstr[9] = 0x30 + (u_el % 10); /* V4 */ + + cmdstr[11] = 0x2F; /* K */ + cmdstr[12] = 0x20; /* END */ + + retval = write_block(&rs->rotport, cmdstr, 13); + if (retval != RIG_OK) { + return retval; + } + + return RIG_OK; +} + +static int spid_rot_get_position(ROT *rot, azimuth_t *az, elevation_t *el) +{ + struct rot_state *rs; + rs = &rot->state; + int retval; + int retry_read = 0; + char posbuf[12]; + + rig_debug(RIG_DEBUG_TRACE, "%s called\n", __FUNCTION__); + + do { + retval = write_block(&rs->rotport, "\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1F\x20", 13); + if (retval != RIG_OK) { + return retval; + } + + memset(posbuf, 0, 12); + retval = read_block(&rs->rotport, posbuf, 12); + } while (retval < 0 && retry_read++ < rot->state.rotport.retry); + if (retval < 0) + return retval; + + *az = posbuf[1] * 100; + *az += posbuf[2] * 10; + *az += posbuf[3]; + *az += posbuf[4] / 10.0; + *az -= 360; + + *el = posbuf[6] * 100; + *el += posbuf[7] * 10; + *el += posbuf[8]; + *el += posbuf[9] / 10.0; + *el -= 360; + + rig_debug(RIG_DEBUG_TRACE, "%s: (az, el) = (%.1f, %.1f)\n", + __FUNCTION__, *az, *el); + + return RIG_OK; +} + +static int spid_rot_stop(ROT *rot) +{ + struct rot_state *rs; + int retval; + rs = &rot->state; + int retry_read = 0; + char posbuf[12]; + + rig_debug(RIG_DEBUG_TRACE, "%s called\n", __FUNCTION__); + + do { + retval = write_block(&rs->rotport, "\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0F\x20", 13); + if (retval != RIG_OK) { + return retval; + } + + memset(posbuf, 0, 12); + retval = read_block(&rs->rotport, posbuf, 12); + } while (retval < 0 && retry_read++ < rot->state.rotport.retry); + if (retval < 0) + return retval; + + return RIG_OK; +} + +const struct confparams spid_cfg_params[] = { + { TOK_AZRES, "az_resolution", "Azimuth resolution", "Number of pulses per degree, 0 = auto sense", + "0", RIG_CONF_NUMERIC, { .n = { 0, 0xff, 1 } } + }, + { TOK_ELRES, "el_resolution", "Eleveation resolution", "Number of pulses per degree, 0 = auto sense", + "0", RIG_CONF_NUMERIC, { .n = { 0, 0xff, 1 } } + }, + { RIG_CONF_END, NULL, } +}; + +const struct rot_caps spid_rot_caps = { + .rot_model = ROT_MODEL_SPID, + .model_name = "Rot2Prog", + .mfg_name = "SPID", + .version = "0.1", + .copyright = "LGPL", + .status = RIG_STATUS_UNTESTED, + .rot_type = ROT_TYPE_OTHER, + .port_type = RIG_PORT_SERIAL, + .serial_rate_min = 600, + .serial_rate_max = 1200, + .serial_data_bits = 8, + .serial_stop_bits = 1, + .serial_parity = RIG_PARITY_NONE, + .serial_handshake = RIG_HANDSHAKE_NONE, + .write_delay = 0, + .post_write_delay = 0, + .timeout = 400, + .retry = 3, + + .min_az = -180.0, + .max_az = 540.0, + .min_el = -20.0, + .max_el = 210.0, + + .cfgparams = spid_cfg_params, + .get_conf = spid_get_conf, + .set_conf = spid_set_conf, + + .rot_init = spid_rot_init, + .rot_cleanup = spid_rot_cleanup, + .get_position = spid_rot_get_position, + .set_position = spid_rot_set_position, + .stop = spid_rot_stop, +}; + +DECLARE_INITROT_BACKEND(spid) +{ + rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __FUNCTION__); + + rot_register(&spid_rot_caps); + + return RIG_OK; +} diff --git a/spid/spid.h b/spid/spid.h new file mode 100644 index 000000000..d6f4e569f --- /dev/null +++ b/spid/spid.h @@ -0,0 +1,28 @@ +/* + * Hamlib Rotator backend - SPID Rot2Prog + * Copyright (c) 2009 by Norvald H. Ryeng, LA6YKA + * + * $Id$ + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _ROT_SPID_H +#define _ROT_SPID_H 1 + +extern const struct rot_caps spid_rot_caps; + +#endif /* _ROT_SPID_H */ diff --git a/spid/spid.txt b/spid/spid.txt new file mode 100644 index 000000000..925567ea8 --- /dev/null +++ b/spid/spid.txt @@ -0,0 +1,208 @@ +SPID Rot2Prog Protocol +====================== + +This is an attempt at documenting the protocol of the Rot2Prog rotator +controller from SPID Elektronik (spid@alpha.pl). The protocol is +supposed to be similar on Rot1Prog and Rot2Prog, but the specification +in this document has only been tested on Rot2Prog. + + + +GENERAL INFO +------------ + +The SPID protocol supports 3 commands: stop, status and set. The stop +command stops the rotator in its current position. The status command +returns the current position of the rotator, and the set command tells +the rotator to rotate to a given position. + +The rotator controller communicates with the PC using a serial +port. Communication parameters are 600 bps, 8 bits, no parity and 1 +stop bit. (Rot1Prog uses 1200 bps.) + +All commands are issued as 13 byte packets, and responses are received +as 12 byte packets. + + + +COMMAND PACKETS +--------------- + +Command packets are 13 byte long. + +Byte: 0 1 2 3 4 5 6 7 8 9 10 11 12 + ----------------------------------------------------------------- +Field: | S | H1 | H2 | H3 | H4 | PH | V1 | V2 | V3 | V4 | PV | K | END | + ----------------------------------------------------------------- +Value: 57 3x 3x 3x 3x 0x 3x 3x 3x 3x 0x xF 20 (hex) + + +S: Start byte. This is always 0x57 ('W') + +H1-H4: Azimuth as ASCII characters 0-9 + +PH: Azimuth resolution in pulses per degree (ignored!) + +V1-V4: Elevation as ASCII characters 0-9 + +PV: Elevation resolution in pulses per degree (ignored!) + +K: Command (0x0F=stop, 0x1F=status, 0x2F=set) + +END: End byte. This is always 0x20 (space) + + +Positions are encoded as number of pulses in ASCII numbers +'0000'-'9999' (see set command for formula). + + + +RESPONSE PACKETS +---------------- + +Response packets are 12 byte long. + +Byte: 0 1 2 3 4 5 6 7 8 9 10 11 + ------------------------------------------------------------- +Field: | S | H1 | H2 | H3 | H4 | PH | V1 | V2 | V3 | V4 | PV | END | + ------------------------------------------------------------- +Value: 57 3x 3x 3x 3x 0x 3x 3x 3x 3x 0x 20 (hex) + + +S: Start byte. This is always 0x57 ('W') + +H1-H4: Azimuth as byte values + +PH: Azimuth resolution in pulses per degree (controller setting) + +V1-V4: Elevation as byte values + +PV: Elevation resolution in pulses per degree (controller setting) + +END: End byte. This is always 0x20 (space) + + +Positions are decoded using the following formulas: + + az = H1 * 100 + H2 * 10 + H3 + H4 / 10 - 360 + + el = V1 * 100 + V2 * 10 + V3 + V4 / 10 - 360 + + +The PH and PV values in the response packet reflect the settings of +the rotator controller. The Rot2Prog supports the following +resolutions (always the same for azimuth and elevation): + + 1 pulse/deg: PH=0x01, PV=0x01 + 0.5 pulse/deg: PH=0x02, PV=0x02 + 0.25 pulse/deg: PH=0x04, PV=0x04 + + + +STOP COMMAND +------------ + +The stop command stops the rotator immediately in the current +position and returns the current position. (The position returned does +not seem to be entirely correct, often off by a degree or two.) + +The H1-H4, PH, V1-V4 and PV fields are ignored, so only the S, K and +END fields are used. E.g.: + + Command: + ----------------------------------------------------------------- + | S | H1 | H2 | H3 | H4 | PH | V1 | V2 | V3 | V4 | PV | K | END | + ----------------------------------------------------------------- + | 57| 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 0F| 20 | (hex) + ----------------------------------------------------------------- + Rotator stops + + Response: + ------------------------------------------------------------- + | S | H1 | H2 | H3 | H4 | PH | V1 | V2 | V3 | V4 | PV | END | + ------------------------------------------------------------- + | 57| 03 | 07 | 02 | 05 | 02 | 03 | 09 | 04 | 00 | 02 | 20 | (hex) + ------------------------------------------------------------- + az=372.5-360=12.5, el=394.0-360=34.0 + PH=PV=0x02 (pulse for each 0.5 deg) + + + +STATUS COMMAND +-------------- + +The status command returns the current position of the antenna. + +The H1-H4, PH, V1-V4 and PV fields are ignored, so only the S, K and +END fields are used. E.g.: + + Command: + ----------------------------------------------------------------- + | S | H1 | H2 | H3 | H4 | PH | V1 | V2 | V3 | V4 | PV | K | END | + ----------------------------------------------------------------- + | 57| 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 1F| 20 | (hex) + ----------------------------------------------------------------- + + Response: + ------------------------------------------------------------- + | S | H1 | H2 | H3 | H4 | PH | V1 | V2 | V3 | V4 | PV | END | + ------------------------------------------------------------- + | 57| 03 | 07 | 02 | 05 | 02 | 03 | 09 | 04 | 00 | 02 | 20 | (hex) + ------------------------------------------------------------- + az=372.5-360=12.5, el=394.0-360=34.0 + PH=PV=0x02 (pulse for each 0.5 deg) + + +Status commands can be issued while the rotator is moving and will +always return the current position. + + + +SET COMMAND +----------- + +The set command tells the rotator to turn to a specific +position. The controller does not send a response to this command. + +Azimuth and elevation is calculated as number of pulses, with a +360 +degree offset (so that negative position can be encoded with positive +numbers). + + H = PH * (360 + az) + + V = PV * (360 + el) + +H1-H4 and V1-V4 are these numbers encoded as ASCII (0x30-0x39, +i.e. '0'-'9'). + +E.g., when pointing to azimuth 123.5, elevation 77.0 when the rotator +sends one pulse per 0.5 degree (PH=PV=2): + + + H = 2 * (360 + 123.5) = 967 + + V = 2 * (360 + 77.0) = 874 + + ----------------------------------------------------------------- + | S | H1 | H2 | H3 | H4 | PH | V1 | V2 | V3 | V4 | PV | K | END | + ----------------------------------------------------------------- + | 57| 30 | 39 | 36 | 37 | 02 | 30 | 38 | 37 | 34 | 02 | 2F| 20 | (hex) + ----------------------------------------------------------------- + +The PH and PV values sent are ignored. The values used by the rotator +control unit are set by choosing resolution in the setup +menu. Luckily, these values can be read using the status command. + + + +SEE ALSO +-------- +http://alfaradio.ca/downloads/program_info/ + + +AUTHOR +------ + +Norvald H. Ryeng, LA6YKA +norvald@ryeng.name +2009-05-21