Implement and document Extended Response Protocol for rotctld

New test program, testrotctld.pl for rotctld
Implemented locator.c functions in rotctl and rotctld



git-svn-id: https://hamlib.svn.sourceforge.net/svnroot/hamlib/trunk@2831 7ae35d74-ebe9-4afe-98af-79ac388436b8
Hamlib-1.2.11
Nate Bargmann, N0NB 2010-02-14 22:18:00 +00:00
rodzic 786e53ce50
commit 121f4a996b
7 zmienionych plików z 1528 dodań i 279 usunięć

Wyświetl plik

@ -5,15 +5,15 @@ DEJATOOL = testfreq testbcd testloc rigctl
DISTCLEANFILES = rigctl.log rigctl.sum testbcd.log testbcd.sum
bin_PROGRAMS = rigctl rigmem rigswr rigsmtr rotctl rigctld rotctld
man_MANS = rigctl.1 rigmem.1 rigswr.1 rigsmtr.1 rotctl.1 rigctld.8 rotctld.8
man_MANS = rigctl.1 rigmem.1 rigswr.1 rigsmtr.1 rotctl.1 rigctld.8 rotctld.8
check_PROGRAMS = dumpmem testrig testtrn testbcd testfreq listrigs \
testloc rig_bench @RIGMATRIX@
rigctl_SOURCES = rigctl.c rigctl_parse.c dumpcaps.c sprintflst.c
rigctld_SOURCES = rigctld.c rigctl_parse.c dumpcaps.c sprintflst.c
rotctl_SOURCES = rotctl.c rotctl_parse.c
rotctld_SOURCES = rotctld.c rotctl_parse.c
rotctl_SOURCES = rotctl.c rotctl_parse.c dumpcaps_rot.c
rotctld_SOURCES = rotctld.c rotctl_parse.c dumpcaps_rot.c
rigswr_SOURCES = rigswr.c
rigsmtr_SOURCES = rigsmtr.c
rigmem_SOURCES = rigmem.c memsave.c memload.c memcsv.c sprintflst.c
@ -40,11 +40,11 @@ rigmem_LDFLAGS = @BACKENDLNK@ @LIBXML2_LIBS@
rotctl_LDFLAGS = @ROT_BACKENDLNK@
rigctld_LDFLAGS = @BACKENDLNK@ @PTHREAD_LIBS@ @NET_LIBS@
rotctld_LDFLAGS = @ROT_BACKENDLNK@ @PTHREAD_LIBS@ @NET_LIBS@
# temporary hack
testbcd_LDFLAGS = -dlpreopen self
testloc_LDFLAGS = -dlpreopen self
# rigmatrix needs also libgd
rigmatrix_LDFLAGS = -lgd -lz @BACKENDLNK@
@ -72,5 +72,5 @@ rigmatrix.html: rigmatrix_head.html rigmatrix listrigs
for f in `./listrigs | tail -n +2 | cut -f1` ; do ( ./rigctl -m $$f -u > sup-info/support/model$$f.txt || exit 0 ) ; done
./rigctl -l |sort -n | $(srcdir)/rig_split_lst.awk -v lst_dir="sup-info"
EXTRA_DIST = rigmatrix_head.html rig_split_lst.awk $(man_MANS) testctld.pl
EXTRA_DIST = rigmatrix_head.html rig_split_lst.awk $(man_MANS) testctld.pl testrotctld.pl

Wyświetl plik

@ -2,26 +2,26 @@
* rotctl.c - (C) Stephane Fillod 2000-2009
*
* This program test/control a rotator using Hamlib.
* It takes commands in interactive mode as well as
* It takes commands in interactive mode as well as
* from command line options.
*
* $Id: rotctl.c,v 1.14 2009-02-17 08:03:22 fillods Exp $
* $Id: rotctl.c,v 1.14 2009-02-17 08:03:22 fillods Exp $
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*
*/
#ifdef HAVE_CONFIG_H
@ -41,13 +41,13 @@
#include "rotctl_parse.h"
/*
/*
* Prototypes
*/
void usage();
/*
* Reminder: when adding long options,
* Reminder: when adding long options,
* keep up to date SHORT_OPTIONS, usage()'s output and man page. thanks.
* NB: do NOT use -W since it's reserved by POSIX.
* TODO: add an option to read from a file
@ -77,7 +77,7 @@ int opt_end= 0 ; /* only used by rotctld */
char send_cmd_term = '\r'; /* send_cmd termination char */
int main (int argc, char *argv[])
{
{
ROT *my_rot; /* handle to rot (instance) */
rot_model_t my_model = ROT_MODEL_DUMMY;
@ -167,7 +167,7 @@ int main (int argc, char *argv[])
"<hamlib-developer@lists.sourceforge.net>\n\n");
/*
* at least one command on command line,
* at least one command on command line,
* disable interactive mode
*/
if (optind < argc)
@ -176,7 +176,7 @@ int main (int argc, char *argv[])
my_rot = rot_init(my_model);
if (!my_rot) {
fprintf(stderr, "Unknown rot num %d, or initialization error.\n",
fprintf(stderr, "Unknown rot num %d, or initialization error.\n",
my_model);
fprintf(stderr, "Please check with --list option.\n");
exit(2);

Wyświetl plik

@ -2,26 +2,26 @@
* rotctl.c - (C) Stephane Fillod 2000-2009
*
* This program test/control a rotator using Hamlib.
* It takes commands in interactive mode as well as
* It takes commands in interactive mode as well as
* from command line options.
*
* $Id: rotctl_parse.c,v 1.5 2009-01-04 14:49:17 fillods Exp $
* $Id: rotctl_parse.c,v 1.5 2009-01-04 14:49:17 fillods Exp $
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*
*/
#ifdef HAVE_CONFIG_H
@ -70,17 +70,20 @@ static pthread_mutex_t rot_mutex = PTHREAD_MUTEX_INITIALIZER;
struct test_table {
unsigned char cmd;
const char *name;
int (*rot_routine)(ROT*, FILE*, int, const struct test_table*, const char*,
const char*, const char*);
int (*rot_routine)(ROT*, FILE*, int, const struct test_table*, const char*,
const char*, const char*, const char*, const char*, const char*);
int flags;
const char *arg1;
const char *arg2;
const char *arg3;
const char *arg4;
const char *arg5;
const char *arg6;
};
#define declare_proto_rot(f) static int (f)(ROT *rot, FILE *fout, int interactive, \
const struct test_table *cmd, const char *arg1, \
const char *arg2, const char *arg3)
const struct test_table *cmd, const char *arg1, const char *arg2, \
const char *arg3, const char *arg4, const char *arg5, const char *arg6)
declare_proto_rot(set_position);
declare_proto_rot(get_position);
@ -92,6 +95,17 @@ declare_proto_rot(get_info);
declare_proto_rot(inter_set_conf); /* interactive mode set_conf */
declare_proto_rot(send_cmd);
declare_proto_rot(dump_state);
declare_proto_rot(dump_caps);
/* Follows are functions from locator.c */
declare_proto_rot(loc2lonlat);
declare_proto_rot(lonlat2loc);
declare_proto_rot(d_m_s2dec);
declare_proto_rot(dec2d_m_s);
declare_proto_rot(d_mm2dec);
declare_proto_rot(dec2d_mm);
declare_proto_rot(coord2qrb);
declare_proto_rot(az_sp2az_lp);
declare_proto_rot(dist_sp2dist_lp);
/*
* convention: upper case cmd is set, lowercase is get
@ -99,24 +113,34 @@ declare_proto_rot(dump_state);
* NB: 'q' 'Q' '?' are reserved by interactive mode interface
*/
struct test_table test_list[] = {
{ 'P', "set_pos", set_position, ARG_IN, "Azimuth", "Elevation" },
{ 'p', "get_pos", get_position, ARG_OUT, "Azimuth", "Elevation" },
{ 'K', "park", park, ARG_NONE, },
{ 'S', "stop", stop, ARG_NONE, },
{ 'R', "reset", reset, ARG_IN, "Reset" },
{ 'M', "move", move, ARG_IN, "Direction", "Speed" },
{ 'C', "set_conf", inter_set_conf, ARG_IN, "Token", "Value" },
{ '_', "get_info", get_info, ARG_OUT, "Info" },
{ 'w', "send_cmd", send_cmd, ARG_IN1|ARG_IN_LINE|ARG_OUT2, "Cmd", "Reply" },
{ 0x8f,"dump_state", dump_state, ARG_OUT },
{ 0x00, "", NULL },
{ 'P', "set_pos", set_position, ARG_IN, "Azimuth", "Elevation" },
{ 'p', "get_pos", get_position, ARG_OUT, "Azimuth", "Elevation" },
{ 'K', "park", park, ARG_NONE, },
{ 'S', "stop", stop, ARG_NONE, },
{ 'R', "reset", reset, ARG_IN, "Reset" },
{ 'M', "move", move, ARG_IN, "Direction", "Speed" },
{ 'C', "set_conf", inter_set_conf, ARG_IN, "Token", "Value" },
{ '_', "get_info", get_info, ARG_OUT, "Info" },
{ 'w', "send_cmd", send_cmd, ARG_IN1|ARG_IN_LINE|ARG_OUT2, "Cmd", "Reply" },
{ '1', "dump_caps", dump_caps, },
{ 0x8f,"dump_state",dump_state, ARG_OUT },
{ 'L', "lonlat2loc",lonlat2loc, ARG_IN1|ARG_IN2|ARG_IN3|ARG_OUT1, "Longitude", "Latitude", "Loc Len [2-12]", "Locator" },
{ 'l', "loc2lonlat",loc2lonlat, ARG_IN1|ARG_OUT1|ARG_OUT2, "Locator", "Longitude", "Latitude" },
{ 'D', "dms2dec", d_m_s2dec, ARG_IN1|ARG_IN2|ARG_IN3|ARG_IN4|ARG_OUT1, "Degrees", "Minutes", "Seconds", "S/W", "Dec Degrees" },
{ 'd', "dec2dms", dec2d_m_s, ARG_IN1|ARG_OUT1|ARG_OUT2|ARG_OUT3|ARG_OUT4, "Dec Degrees", "Degrees", "Minutes", "Seconds", "S/W" },
{ 'E', "dmmm2dec", d_mm2dec, ARG_IN1|ARG_IN2|ARG_IN3|ARG_OUT1, "Degrees", "Dec Minutes", "S/W", "Dec Deg" },
{ 'e', "dec2dmmm", dec2d_mm, ARG_IN1|ARG_OUT1|ARG_OUT2|ARG_OUT3, "Dec Deg", "Degrees", "Dec Minutes", "S/W" },
{ 'B', "qrb", coord2qrb, ARG_IN1|ARG_IN2|ARG_IN3|ARG_IN4|ARG_OUT1|ARG_OUT2, "Lon 1", "Lat 1", "Lon 2", "Lat 2", "QRB Distance", "QRB Azimuth" },
{ 'A', "a_sp2a_lp", az_sp2az_lp, ARG_IN1|ARG_OUT1, "Short Path Deg", "Long Path Deg" },
{ 'a', "d_sp2d_lp", dist_sp2dist_lp,ARG_IN1|ARG_OUT1, "Short Path km", "Long Path km" },
{ 0x00, "", NULL },
};
struct test_table *find_cmd_entry(int cmd)
{
int i;
for (i=0; i<MAXNBOPT && test_list[i].cmd != 0x00; i++)
for (i = 0; i < MAXNBOPT && test_list[i].cmd != 0x00; i++)
if (test_list[i].cmd == cmd)
break;
@ -131,13 +155,13 @@ struct test_table *find_cmd_entry(int cmd)
char parse_arg(const char *arg)
{
int i;
for (i=0; i<MAXNBOPT && test_list[i].cmd != 0; i++)
for (i = 0; i < MAXNBOPT && test_list[i].cmd != 0; i++)
if (!strncmp(arg, test_list[i].name, MAXNAMSIZ))
return test_list[i].cmd;
return 0;
}
/*
/*
* This scanf works even in presence of signals (timer, SIGIO, ..)
*/
static int scanfc(FILE *fin, const char *format, void *p)
@ -161,16 +185,20 @@ extern int interactive;
extern int prompt;
extern int opt_end;
extern char send_cmd_term;
int ext_resp = 0;
unsigned char resp_sep = '\n'; /* Default response separator */
int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
{
{
int retcode; /* generic return code from functions */
unsigned char cmd;
struct test_table *cmd_entry;
char arg1[MAXARGSZ+1], *p1;
char arg2[MAXARGSZ+1], *p2;
char arg3[MAXARGSZ+1], *p3;
char arg1[MAXARGSZ + 1], *p1;
char arg2[MAXARGSZ + 1], *p2;
char arg3[MAXARGSZ + 1], *p3;
char arg4[MAXARGSZ + 1], *p4;
char *p5, *p6;
static int last_was_ret = 1;
if (interactive) {
@ -181,11 +209,31 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
if (scanfc(fin, "%c", &cmd) < 0)
return -1;
/* Extended response protocol requested with leading '+' on command
* string -- rotctld only! */
if (cmd == '+' && !prompt) {
ext_resp = 1;
if (scanfc(fin, "%c", &cmd) < 0)
return -1;
} else if (cmd == '+' && prompt) {
return 0;
}
if (cmd != '\\' && cmd != '_' && ispunct(cmd) && !prompt) {
ext_resp = 1;
resp_sep = cmd;
if (scanfc(fin, "%c", &cmd) < 0)
return -1;
continue;
} else if (cmd != '\\' && cmd != '?' && cmd != '_' && ispunct(cmd) && prompt) {
return 0;
}
/* command by name */
if (cmd == '\\') {
unsigned char cmd_name[MAXNAMSIZ], *pcmd = cmd_name;
int c_len = MAXNAMSIZ;
if (scanfc(fin, "%c", pcmd) < 0)
return -1;
@ -202,7 +250,6 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
if (last_was_ret) {
if (prompt) {
fprintf(fout, "? for help, q to quit.\n");
fprintf(fout, "\nRotator command: ");
}
return 0;
}
@ -243,9 +290,9 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
return 0;
}
p1 = p2 = p3 = NULL;
p1 = p2 = p3 = p4 = p5 = p6 = NULL;
if ((cmd_entry->flags & ARG_IN_LINE) &&
if ((cmd_entry->flags & ARG_IN_LINE) &&
(cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) {
if (interactive) {
char *nl;
@ -259,7 +306,7 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
p1 = arg1[0]==' '?arg1+1:arg1;
} else {
if (!argv[optind]) {
fprintf(stderr, "Invalid arg for command '%s'\n",
fprintf(stderr, "Invalid arg for command '%s'\n",
cmd_entry->name);
exit(2);
}
@ -274,7 +321,7 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
p1 = arg1;
} else {
if (!argv[optind]) {
fprintf(stderr, "Invalid arg for command '%s'\n",
fprintf(stderr, "Invalid arg for command '%s'\n",
cmd_entry->name);
exit(2);
}
@ -290,7 +337,7 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
p2 = arg2;
} else {
if (!argv[optind]) {
fprintf(stderr, "Invalid arg for command '%s'\n",
fprintf(stderr, "Invalid arg for command '%s'\n",
cmd_entry->name);
exit(2);
}
@ -306,7 +353,7 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
p3 = arg3;
} else {
if (!argv[optind]) {
fprintf(stderr, "Invalid arg for command '%s'\n",
fprintf(stderr, "Invalid arg for command '%s'\n",
cmd_entry->name);
exit(2);
}
@ -314,6 +361,23 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
}
}
if (p1 && p1[0]!='?' && (cmd_entry->flags & ARG_IN4) && cmd_entry->arg4) {
if (interactive) {
if (prompt)
fprintf(fout, "%s: ", cmd_entry->arg4);
if (scanfc(fin, "%s", arg4) < 0)
return -1;
p4 = arg4;
} else {
if (!argv[optind]) {
fprintf(stderr, "Invalid arg for command '%s'\n",
cmd_entry->name);
exit(2);
}
p4 = argv[optind++];
}
}
/*
* mutex locking needed because rigctld is multithreaded
* and hamlib is not MT-safe
@ -323,28 +387,60 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc)
#endif
if (!prompt)
rig_debug(RIG_DEBUG_TRACE, "rotctl: %c '%s' '%s' '%s'\n",
cmd, p1, p2, p3);
rig_debug(RIG_DEBUG_TRACE, "rotctl(d): %c '%s' '%s' '%s' '%s'\n",
cmd, p1, p2, p3, p4);
retcode = (*cmd_entry->rot_routine)(my_rot, fout, interactive,
cmd_entry, p1, p2, p3);
/*
* Extended Response protocol: output received command name and arguments
* response.
*/
if (interactive && ext_resp && !prompt) {
char a1[MAXARGSZ + 1];
char a2[MAXARGSZ + 1];
char a3[MAXARGSZ + 1];
char a4[MAXARGSZ + 1];
p1 == NULL ? a1[0] = '\0' : snprintf(a1, sizeof(a1), " %s", p1);
p2 == NULL ? a2[0] = '\0' : snprintf(a2, sizeof(a2), " %s", p2);
p3 == NULL ? a3[0] = '\0' : snprintf(a3, sizeof(a3), " %s", p3);
p4 == NULL ? a4[0] = '\0' : snprintf(a4, sizeof(a4), " %s", p4);
fprintf(fout, "%s:%s%s%s%s%c", cmd_entry->name, a1, a2, a3, a4, resp_sep);
}
retcode = (*cmd_entry->rot_routine)(my_rot, fout, interactive,
cmd_entry, p1, p2, p3, p4, p5, p6);
#ifdef HAVE_PTHREAD
pthread_mutex_unlock(&rot_mutex);
#endif
if (retcode != RIG_OK) {
if (interactive && !prompt)
fprintf(fout, NETROTCTL_RET "%d\n", retcode); /* only for rotctld */
/* only for rotctld */
if (interactive && !prompt) {
fprintf(fout, NETROTCTL_RET "%d\n", retcode);
ext_resp = 0;
resp_sep = '\n';
}
else
fprintf(fout, "%s: error = %s\n", cmd_entry->name, rigerror(retcode));
fprintf(fout, "%s: error = %s\n", cmd_entry->name, rigerror(retcode));
} else {
if (interactive && !prompt) { /* only for rotctld */
if (!(cmd_entry->flags & ARG_OUT) && !opt_end) /* netrotctl RIG_OK */
/* only for rotctld */
if (interactive && !prompt) {
/* netrotctl RIG_OK */
if (!(cmd_entry->flags & ARG_OUT)
&& !opt_end && !ext_resp && cmd != 0xf0)
fprintf(fout, NETROTCTL_RET "0\n");
else if ((cmd_entry->flags & ARG_OUT) && opt_end) /* Nate's protocol */
/* block marker protocol */
else if (ext_resp && cmd != 0xf0) {
fprintf(fout, NETROTCTL_RET "0\n");
ext_resp = 0;
resp_sep = '\n';
}
/* Nate's protocol (obsolete) */
else if ((cmd_entry->flags & ARG_OUT) && opt_end)
fprintf(fout, "END\n");
}
}
}
fflush(fout);
@ -360,26 +456,31 @@ void version()
printf("%s\n", hamlib_copyright);
}
void usage_rot(FILE *fout)
{
int i;
int i, nbspaces;
fprintf(fout, "Commands (may not be available for this rotator):\n");
for (i=0; test_list[i].cmd != 0; i++) {
fprintf(fout, "%c: %-16s(", test_list[i].cmd, test_list[i].name);
if (test_list[i].arg1)
fprintf(fout, "%s", test_list[i].arg1);
if (test_list[i].arg2)
fprintf(fout, ",%s", test_list[i].arg2);
if (test_list[i].arg3)
fprintf(fout, ",%s", test_list[i].arg3);
fprintf(fout, ") \t");
fprintf(fout, "Commands (some may not be available for this rig):\n");
for (i = 0; test_list[i].cmd != 0; i++) {
fprintf(fout, "%c: %-12s(", isprint(test_list[i].cmd) ?
test_list[i].cmd : '?', test_list[i].name);
if (i%2)
fprintf(fout, "\n");
nbspaces = 16;
if (test_list[i].arg1 && (test_list[i].flags&ARG_IN1))
nbspaces -= fprintf(fout, "%s", test_list[i].arg1);
if (test_list[i].arg2 && (test_list[i].flags&ARG_IN2))
nbspaces -= fprintf(fout, ", %s", test_list[i].arg2);
if (test_list[i].arg3 && (test_list[i].flags&ARG_IN3))
nbspaces -= fprintf(fout, ", %s", test_list[i].arg3);
if (test_list[i].arg4 && (test_list[i].flags&ARG_IN4))
nbspaces -= fprintf(fout, ", %s", test_list[i].arg4);
fprintf(fout, ")\n");
}
}
int print_conf_list(const struct confparams *cfp, rig_ptr_t data)
{
ROT *rot = (ROT*) data;
@ -387,33 +488,33 @@ int print_conf_list(const struct confparams *cfp, rig_ptr_t data)
char buf[128] = "";
rot_get_conf(rot, cfp->token, buf);
printf("%s: \"%s\"\n" "\tDefault: %s, Value: %s\n",
cfp->name, cfp->tooltip,
printf("%s: \"%s\"\n" "\tDefault: %s, Value: %s\n",
cfp->name, cfp->tooltip,
cfp->dflt, buf );
switch (cfp->type) {
case RIG_CONF_NUMERIC:
printf("\tRange: %.1f..%.1f, step %.1f\n",
printf("\tRange: %.1f..%.1f, step %.1f\n",
cfp->u.n.min, cfp->u.n.max, cfp->u.n.step);
break;
case RIG_CONF_COMBO:
if (!cfp->u.c.combostr)
break;
break;
printf("\tCombo: %s", cfp->u.c.combostr[0]);
for (i=1 ; i<RIG_COMBO_MAX && cfp->u.c.combostr[i]; i++)
printf(", %s", cfp->u.c.combostr[i]);
for (i = 1 ; i < RIG_COMBO_MAX && cfp->u.c.combostr[i]; i++)
printf(", %s", cfp->u.c.combostr[i]);
printf("\n");
break;
default:
break;
default:
break;
}
return 1; /* !=0, we want them all ! */
return 1; /* != 0, we want them all ! */
}
static int print_model_list(const struct rot_caps *caps, void *data)
{
printf("%d\t%-14s%-16s%-8s%s\n", caps->rot_model, caps->mfg_name,
printf("%-8d%-16s%-25s%-10s%s\n", caps->rot_model, caps->mfg_name,
caps->model_name, caps->version, rig_strstatus(caps->status));
return 1; /* !=0, we want them all ! */
}
@ -424,7 +525,7 @@ void list_models()
rot_load_all_backends();
printf("Rot#\tMfg Model Vers.\n");
printf("Rot# Mfg Model Vers.\n");
status = rot_list_foreach(print_model_list, NULL);
if (status != RIG_OK ) {
printf("rot_list_foreach: error = %s \n", rigerror(status));
@ -459,6 +560,7 @@ int set_conf(ROT *my_rot, char *conf_parms)
* static int (f)(ROT *rot, int interactive, const void *arg1, const void *arg2, const void *arg3, const void *arg4)
*/
/* 'P' */
declare_proto_rot(set_position)
{
azimuth_t az;
@ -469,6 +571,7 @@ declare_proto_rot(set_position)
return rot_set_position(rot, az, el);
}
/* 'p' */
declare_proto_rot(get_position)
{
int status;
@ -477,28 +580,30 @@ declare_proto_rot(get_position)
status = rot_get_position(rot, &az, &el);
if (status != RIG_OK)
return status;
if (interactive && prompt)
return status;
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg1);
fprintf(fout, "%f\n", az);
if (interactive && prompt)
fprintf(fout, "%f%c", az, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg2);
fprintf(fout, "%f\n", el);
fprintf(fout, "%f%c", el, resp_sep);
return status;
}
/* 'S' */
declare_proto_rot(stop)
{
return rot_stop(rot);
}
/* 'K' */
declare_proto_rot(park)
{
return rot_park(rot);
}
/* 'R' */
declare_proto_rot(reset)
{
rot_reset_t reset;
@ -507,28 +612,31 @@ declare_proto_rot(reset)
return rot_reset(rot, reset);
}
/* '_' */
declare_proto_rot(get_info)
{
const char *s;
s = rot_get_info(rot);
if (interactive && prompt)
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg1);
fprintf(fout, "%s\n", s ? s : "None");
fprintf(fout, "%s%c", s ? s : "None", resp_sep);
return RIG_OK;
}
/* 'M' */
declare_proto_rot(move)
{
int direction;
int speed;
int direction;
int speed;
sscanf(arg1, "%d", &direction);
sscanf(arg1, "%d", &direction);
sscanf(arg2, "%d", &speed);
return rot_move(rot, direction, speed);
}
/* 'C' */
declare_proto_rot(inter_set_conf)
{
token_t token;
@ -539,7 +647,19 @@ declare_proto_rot(inter_set_conf)
return rot_set_conf(rot, token, val);
}
/* For rotctld internal use */
/* '1' */
declare_proto_rot(dump_caps)
{
dumpcaps_rot(rot, fout);
return RIG_OK;
}
/* For rotctld internal use
* '0x8f'
*/
declare_proto_rot(dump_state)
{
struct rot_state *rs = &rot->state;
@ -548,13 +668,29 @@ declare_proto_rot(dump_state)
* - Protocol version
*/
#define ROTCTLD_PROT_VER 0
fprintf(fout, "%d\n", ROTCTLD_PROT_VER);
fprintf(fout, "%d\n", rot->caps->rot_model);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "rotctld Protocol Ver: ");
fprintf(fout, "%d%c", ROTCTLD_PROT_VER, resp_sep);
fprintf(fout, "%lf\n", rs->min_az);
fprintf(fout, "%lf\n", rs->max_az);
fprintf(fout, "%lf\n", rs->min_el);
fprintf(fout, "%lf\n", rs->max_el);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "Rotor Model: ");
fprintf(fout, "%d%c", rot->caps->rot_model, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "Minimum Azimuth: ");
fprintf(fout, "%lf%c", rs->min_az, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "Maximum Azimuth: ");
fprintf(fout, "%lf%c", rs->max_az, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "Minimum Elevation: ");
fprintf(fout, "%lf%c", rs->min_el, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "Maximum Elevation: ");
fprintf(fout, "%lf%c", rs->max_el, resp_sep);
return RIG_OK;
}
@ -584,21 +720,21 @@ declare_proto_rot(send_cmd)
if (send_cmd_term == -1 || backend_num == -1) {
const char *p = arg1, *pp = NULL;
int i;
for (i=0; i < BUFSZ-1 && p != pp; i++) {
pp = p+1;
bufcmd[i] = strtol(p+1, (char **) &p, 0);
for (i = 0; i < BUFSZ - 1 && p != pp; i++) {
pp = p + 1;
bufcmd[i] = strtol(p + 1, (char **) &p, 0);
}
/* must save length to allow 0x00 to be sent as part of a command
*/
cmd_len = i-1;
cmd_len = i - 1;
/* no End Of Message chars */
eom_buf[0] = '\0';
} else {
/* text protocol */
strncpy(bufcmd,arg1,BUFSZ);
bufcmd[BUFSZ-2] = '\0';
strncpy(bufcmd,arg1, BUFSZ);
bufcmd[BUFSZ - 2] = '\0';
cmd_len = strlen(bufcmd);
@ -627,12 +763,12 @@ declare_proto_rot(send_cmd)
retval = read_string(&rs->rotport, buf, BUFSZ, eom_buf, strlen(eom_buf));
if (retval < 0)
break;
if (retval < BUFSZ)
buf[retval] = '\0';
else
buf[BUFSZ-1] = '\0';
fprintf(fout, "%s\n", buf);
} while (retval > 0);
@ -643,3 +779,207 @@ declare_proto_rot(send_cmd)
return retval;
}
/* 'L' */
declare_proto_rot(lonlat2loc)
{
unsigned char loc[MAXARGSZ + 1];
double lat, lon;
int err, pair;
sscanf(arg1, "%lf", &lon);
sscanf(arg2, "%lf", &lat);
sscanf(arg3, "%d", &pair);
pair /= 2;
err = longlat2locator(lon, lat, (char *)&loc, pair);
if (err != RIG_OK)
return err;
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg4);
fprintf(fout, "%s%c", loc, resp_sep);
return err;
}
/* 'l' */
declare_proto_rot(loc2lonlat)
{
unsigned char loc[MAXARGSZ + 1];
double lat, lon;
int status;
sscanf(arg1, "%s", (char *)&loc);
status = locator2longlat(&lon, &lat, (const char *)loc);
if (status != RIG_OK)
return status;
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg2);
fprintf(fout, "%f%c", lon, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg3);
fprintf(fout, "%f%c", lat, resp_sep);
return status;
}
/* 'D' */
declare_proto_rot(d_m_s2dec)
{
int deg, min, sw;
double sec, dec_deg;
sscanf(arg1, "%d", &deg);
sscanf(arg2, "%d", &min);
sscanf(arg3, "%lf", &sec);
sscanf(arg4, "%d", &sw);
dec_deg = dms2dec(deg, min, sec, sw);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg5);
fprintf(fout, "%lf%c", dec_deg, resp_sep);
return RIG_OK;
}
/* 'd' */
declare_proto_rot(dec2d_m_s)
{
int deg, min, sw, err;
double sec, dec_deg;
sscanf(arg1, "%lf", &dec_deg);
err = dec2dms(dec_deg, &deg, &min, &sec, &sw);
if (err != RIG_OK)
return err;
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg2);
fprintf(fout, "%d%c", deg, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg3);
fprintf(fout, "%d%c", min, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg4);
fprintf(fout, "%lf%c", sec, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg5);
fprintf(fout, "%d%c", sw, resp_sep);
return err;
}
/* 'E' */
declare_proto_rot(d_mm2dec)
{
int deg, sw;
double dec_deg, min;
sscanf(arg1, "%d", &deg);
sscanf(arg2, "%lf", &min);
sscanf(arg3, "%d", &sw);
dec_deg = dmmm2dec(deg, min, sw);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg4);
fprintf(fout, "%lf%c", dec_deg, resp_sep);
return RIG_OK;
}
/* 'e' */
declare_proto_rot(dec2d_mm)
{
int deg, sw, err;
double min, dec_deg;
sscanf(arg1, "%lf", &dec_deg);
err = dec2dmmm(dec_deg, &deg, &min, &sw);
if (err != RIG_OK)
return err;
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg2);
fprintf(fout, "%d%c", deg, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg3);
fprintf(fout, "%lf%c", min, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg4);
fprintf(fout, "%d%c", sw, resp_sep);
return err;
}
/* 'B' */
declare_proto_rot(coord2qrb)
{
double lon1, lat1, lon2, lat2, dist, az;
int err;
sscanf(arg1, "%lf", &lon1);
sscanf(arg2, "%lf", &lat1);
sscanf(arg3, "%lf", &lon2);
sscanf(arg4, "%lf", &lat2);
err = qrb(lon1, lat1, lon2, lat2, &dist, &az);
if (err != RIG_OK)
return err;
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg5);
fprintf(fout, "%lf%c", dist, resp_sep);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg6);
fprintf(fout, "%lf%c", az, resp_sep);
return err;
}
/* 'A' */
declare_proto_rot(az_sp2az_lp)
{
double az_sp, az_lp;
sscanf(arg1, "%lf", &az_sp);
az_lp = azimuth_long_path(az_sp);
if (az_lp < 0)
return -RIG_EINVAL;
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg2);
fprintf(fout, "%lf%c", az_lp, resp_sep);
return RIG_OK;
}
/* 'a' */
declare_proto_rot(dist_sp2dist_lp)
{
double dist_sp, dist_lp;
sscanf(arg1, "%lf", &dist_sp);
dist_lp = distance_long_path(dist_sp);
if ((interactive && prompt) || (interactive && !prompt && ext_resp))
fprintf(fout, "%s: ", cmd->arg2);
fprintf(fout, "%lf%c", dist_lp, resp_sep);
return RIG_OK;
}

Wyświetl plik

@ -2,26 +2,26 @@
* rotctl_parse.h - (C) Stephane Fillod 2000-2008
*
* This program test/control a radio using Hamlib.
* It takes commands in interactive mode as well as
* It takes commands in interactive mode as well as
* from command line options.
*
* $Id: rotctl_parse.h,v 1.1 2008-09-12 22:55:09 fillods Exp $
* $Id: rotctl_parse.h,v 1.1 2008-09-12 22:55:09 fillods Exp $
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*
*/
#ifndef ROTCTL_PARSE_H
@ -30,7 +30,14 @@
#include <stdio.h>
#include <hamlib/rotator.h>
/*
/*
* external prototype
*/
int dumpcaps_rot (ROT *, FILE *);
/*
* Prototypes
*/
void usage_rot(FILE *);

Wyświetl plik

@ -2,7 +2,7 @@
.\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1)
.TH ROTCTLD "8" "January 14, 2009" "Hamlib" "Rotator Control Daemon"
.TH ROTCTLD "8" "February 14, 2010" "Hamlib" "Rotator Control Daemon"
.\" Please adjust this date whenever revising the manpage.
.\"
.\" Some roff macros, for reference:
@ -16,19 +16,21 @@
.\" .sp <n> insert n+1 empty lines
.\" for manpage-specific macros, see man(7)
.SH NAME
rotctld \- Hamlib rotator control daemon
rotctld \- Hamlib TCP rotator control daemon
.SH SYNOPSIS
.B rotctld
[\fIOPTION\fR]...
.SH DESCRIPTION
The \fBrotctld\fP program is an EXPERIMENTAL \fBHamlib\fP rotator daemon that
handles TCP client requests. This allows multiple user programs to share one
rotator. Multiple rotators can be controlled on different TCP ports. The syntax
of the commands are the same as \fBrotctl\fP. It is hoped that \fBrotctld\fP
will be especially useful for languages such as Perl, Python, and others.
The \fBrotctld\fP program is an NEW \fBHamlib\fP rotator control daemon ready for
testing that handles client requests via TCP sockets. This allows multiple user
programs to share one rotator (this needs testing). Multiple rotators can be
controlled on different TCP ports by use of multiple \fBrotctld\fP processes. The
syntax of the commands are the same as \fBrotctl\fP. It is hoped that \fBrotctld\fP
will be especially useful for client authors using languages such as Perl, Python,
PHP, and others.
.PP
.\" TeX users may be more comfortable with the \fB<whatever>\fP and
.\" \fI<whatever>\fP escape sequences to invoke bold face and italics,
.\" \fI<whatever>\fP escape sequences to invoke bold face and italics,
.\" respectively.
\fBrotctld\fP communicates to a client through a TCP socket using text
commands shared with \fBrotctl\fP. The protocol is simple, commands are sent
@ -37,20 +39,28 @@ the requested values, one per line, when successful, otherwise, it responds
with one line "RPTR x", where x is a negative number indicating the error code.
Commands that do not return values respond with the line "RPTR x", where x
is zero when successful, otherwise is a regative number indicating the error code.
Each line is terminated with a newline '\\n' character.
Each line is terminated with a newline '\\n' character. This protocol is primarily
for use by the \fINET rotctl\fP (rot model 2) backend.
.PP
Keep in mind that \fBHamlib\fP is BETA level software.
A separate \fBExtended Response\fP protocol extends the above
behavior by echoing the received command string as a header, any returned values
as a key: value pair, and the "RPTR x" string as the end of response marker which
includes the \fBHamlib\fP success or failure value. See the \fIPROTOCOL\fP
section for details. Consider using this protocol for clients that will interact
with \fBrotctld\fP directly through a TCP socket.
.PP
Keep in mind that \fBHamlib\fP is BETA level software.
While a lot of backend libraries lack complete rotator support, the basic functions
are usually well supported. The API may change without publicized notice,
are usually well supported. The API may change without publicized notice,
while an advancement of the minor version (e.g. 1.1.x to 1.2.x) indicates such
a change.
.PP
Please report bugs and provide feedback at the e-mail address given in the
Please report bugs and provide feedback at the e-mail address given in the
REPORTING BUGS section. Patches and code enhancements are also welcome.
.SH OPTIONS
This program follows the usual GNU command line syntax, with long
options starting with two dashes ('-').
.PP
Here is a summary of the supported options:
.TP
.B \-m, --model=id
@ -58,13 +68,19 @@ Select rotator model number. See -l, "list" option below.
.TP
.B \-r, --rot-file=device
Use \fIdevice\fP as the file name of the port the rotator is connected.
Often a serial port, but could be a USB to serial adapter. Typically
/dev/ttyS0, /dev/ttyS1, /dev/ttyUSB0, etc.
Often a serial port, but could be a USB to serial adapter or USB port device.
Typically /dev/ttyS0, /dev/ttyS1, /dev/ttyUSB0, etc.
.TP
.B \-s, --serial-speed=baud
Set serial speed to \fIbaud\fP rate. Uses maximum serial speed from rotor
backend capabilities (set by -m above) as the default.
.TP
.B \-T, --listen-addr=IPADDR
Use \fIIPADDR\fP as the listening IP address. The default is ANY.
.TP
.B \-t, --port=number
Use \fInumber\fP as the TCP listening port. The default is 4533.
.TP
.B \-L, --show-conf
List all config parameters for the rotator defined with -m above.
.TP
@ -73,18 +89,18 @@ Set config parameter. e.g. --set-conf=stop_bits=2
.br
Use -L option for a list.
.TP
.B \-e, --end-marker
Use END marker in rotctld protocol.
.TP
.B \-t, --port=number
Use \fInumber\fP as the TCP listening port. The default is 4533.
.TP
.B \-T, --listen-addr=IPADDR
Use \fIIPADDR\fP as the listening IP address. The default is ANY.
.TP
.B \-l, --list
List all model numbers defined in \fBHamlib\fP and exit.
.TP
.B \-u, --dump-caps
Dump capabilities for the radio defined with -m above and exit.
.TP
.B \-e, --end-marker
Use END marker in rotctld protocol.
.br
N.B.: This option can be considered obsolete. Please consider using the block
protocol instead (see \fIPROTOCOL\fP below).
.TP
.B \-v, --verbose
Set verbose mode, cumulative (see DIAGNOSTICS below).
.TP
@ -95,109 +111,302 @@ Show a summary of these options and exit.
Show the version of \fBrotctld\fP and exit.
.PP
\fBN.B.\fP Some options may not be implemented by a given backend and will
return an error. This is most likely to occur with the \fI\-\-set-conf\fP
return an error. This is most likely to occur with the \fI\-\-set-conf\fP
and \fI\-\-show-conf\fP options.
.PP
Please note that the backend for the rotator to be controlled,
or the rotator itself may not support some commands. In that case,
Please note that the backend for the rotator to be controlled,
or the rotator itself may not support some commands. In that case,
the operation will fail with a \fBHamlib\fP error code.
.SH COMMANDS
Commands can be sent over the TCP socket either as a single char, or as a
long command name plus the value(s) on one '\\n' terminated line. See
PROTOCOL.
Commands can be sent over the TCP socket either as a single char, or as a
long command name plus the value(s) space separated on one '\\n' terminated
line. See \fIPROTOCOL\fP.
.PP
Since most of the \fBHamlib\fP operations have a \fIset\fP and a \fIget\fP method,
an upper case letter will be used for \fIset\fP method whereas the
an upper case letter will be used for \fIset\fP methods whereas the
corresponding lower case letter refers to the \fIget\fP method. Each operation
also has a long name, prepend a backslash to send a long command name.
also has a long name; prepend a backslash to send a long command name.
.PP
Please note that the backend for the rotator to be controlled,
or the rotator itself may not support some commands. In that case,
the operation will fail with a \fBHamlib\fP error message.
Example (Perl): `print $socket "\\\\dump_caps\\n";' to see what the rotor's
backend can do (NOTE: In Perl and many other languages a '\\' will need to be
escaped with a preceding '\\' so that even though two backslash characters
appear in the code, only one will be passed to \fBrotctld\fP. This is a
possible bug!).
.PP
Here is a summary of the supported commands:
Please note that the backend for the rotator to be controlled, or the rotator
itself may not support some commands. In that case, the operation will fail
with a \fBHamlib\fP error message.
.PP
Here is a summary of the supported commands (In the case of "set" commands the
quoted string is replaced by the value in the description. In the case of "get"
commands the quoted string is the key name of the value returned.):
.TP
.B P, set_pos
Set position: azimuth and elevation.
.B P, set_pos 'Azimuth' 'Elevation'
Set position: Azimuth and Elevation as double precision floating point values.
.TP
.B p, get_pos
Get position: azimuth and elevation.
Get position: 'Azimuth' and 'Elevation' as double precision floating point
values.
.TP
.B K, park
Park the antenna.
.TP
.B M, move 'Direction' 'Speed'
Move the rotator in a specific direction at the given rate.
.sp
Values are integers where Direction is defined as 2 = Up, 4 = Down, 8 = Left,
and 16 = Right. Speed is an integer between 1 and 100. Not all backends that
implement the move command use the Speed value. At this time only the gs232a
utilizes the Speed parameter.
.TP
.B S, stop
Stop the rotator.
.B K, park
Park the antenna.
.TP
.B R, reset
.B C, set_conf 'Token' 'Value'
Set Token to Value.
.sp
Backend dependent. Needs testing.
.TP
.B R, reset 'Reset'
Reset the rotator.
.TP
.B M, move
Move the rotator in a specific direction.
.sp
Integer value of '1' for Reset All.
.TP
.B _, get_info
Get misc information about the rotator.
.sp
At the moment returns 'Model Name'.
.TP
.B w, send_cmd
Send raw command string to rotator.
.br
For binary protocols enter values as \\0xAA\\0xBB
.B w, send_cmd 'Cmd'
Send raw command string to rotator.
.sp
For binary protocols enter values as \\0xAA\\0xBB. Expect a 'Reply' from the
rotator which will likely be a binary block or an ASCII string.
.PP
\fBLocator Commands\fP
.PP
These commands offer conversions of Degrees Minutes Seconds to other formats,
Maidenhead square locator conversions and distance and azimuth conversions.
.TP
.B L, lonlat2loc 'Longitude' 'Latitude' 'Loc Len [2-12]'
Returns the Maidenhead locator for the given 'Longitude' and 'Latitude'.
.sp
Both are floating point values. The precision of the returned square is
controlled by 'Loc Len' which should be an even numbered integer value between
2 and 12.
.sp
For example, "+L -170.000000 -85.000000 12\\n" returns "Locator: AA55AA00AA00\\n".
.TP
.B l, loc2lonlat 'Locator'
Returns 'Longitude' and 'Latitude' in decimal degrees at the approximate
center of the requested grid square (despite the use of double precision
variables internally, some rounding error occurs). West longitude is
expressed as a negative value. South latitude is expressed as a negative
value. Locator can be from 2 to 12 characters in length.
.sp
For example, "+l AA55AA00AA00\\n" returns "Longitude: -169.999983\\nLatitude:
-84.999991\\n".
.TP
.B D, dms2dec 'Degrees' 'Minutes' 'Seconds' 'S/W'
Returns 'Dec Degrees', a signed floating point value.
.sp
Degrees and Minutes are
integer values and Seconds is a floating point value. S/W is a flag with '1'
indicating South latitude or West longitude and '0' North or East (the flag is
needed as computers don't recognize a signed zero even though only the Degrees
value only is typically signed in DMS notation).
.TP
.B d, dec2dms 'Dec Degrees'
Returns 'Degrees' 'Minutes' 'Seconds' 'S/W'.
.sp
Values are as in dms2dec above.
.TP
.B E, dmmm2dec 'Degrees' 'Dec Minutes' 'S/W'
Returns 'Dec Degrees', a signed floating point value.
.sp
Degrees is an integer
value and Minutes is a floating point value. S/W is a flag with '1'
indicating South latitude or West longitude and '0' North or East (the flag is
needed as computers don't recognize a signed zero even though only the Degrees
value only is typically signed in DMS notation).
.TP
.B e, dec2dmmm 'Dec Deg'
Returns 'Degrees' 'Minutes' 'S/W'.
.sp
Values are as in dmmm2dec above.
.TP
.B B, qrb 'Lon 1' 'Lat 1' 'Lon 2' 'Lat 2'
Returns 'Distance' 'Azimuth' where Distance is in km and Azimuth is in degrees.
.sp
All Lon/Lat values are signed floating point numbers.
.TP
.B A, a_sp2a_lp 'Short Path Deg'
Returns 'Long Path Deg' or -RIG_EINVAL upon input error..
.sp
Both are floating point values within the range 0.00 to 360.00.
.TP
.B a, d_sp2d_lp 'Short Path km'
Returns 'Long Path km'.
.sp
Both are floating point values.
.SH PROTOCOL
\fBDefault Protocol\fP
.PP
The \fBrotctld\fP protocol is intentionally simple. Commands are entered on
a single line with any needed values. In Perl, reliable results are obtained
by terminating each command string with a newline character, '\\n'.
.PP
.sp
Example \fIset\fP (Perl code):
.sp
print $socket "P 135 10\\n";
.br
.sp
print $socket "\\\\set_pos 135 10\\n"; # escape leading '\\'
.PP
Responses from \fBrotctld\fP are text values and match the same tokens used
in the \fIset\fP commands. Each value is returned on its own line. To
signal the end of a response the "END\\n" string is sent when the '-e' option
is passed.
A one line response will be sent to \fIset\fP commands, "RPTR \fIx\fP\\n"
where \fIx\fP is the Hamlib error code with '0' indicating success of the
command.
.PP
Responses from \fBrotctld\fP \fIget\fP commands are text values and match the
same tokens used in the \fIset\fP commands. Each value is returned on its own
line. On error the string "RPTR \fIx\fP\\n" where \fIx\fP is the Hamlib error
code.
.sp
Example \fIget\fP (Perl code):
.sp
print $socket "p\\n";
.sp
"135"
.br
"10"
.br
"END"
.PP
Most \fIget\fP functions return one to three values.
Future work will focus on making this output compatible with assignment to a
hash, dictionary, or other key:value variable.
Most \fIget\fP functions return one to three values. A notable exception is
the \fI\\dump_caps\fP function which returns many lines of key:value pairs.
.PP
This protocol is primarily used by the \fINET rotctl\fP (rotctl model 2) backend
which allows applications already written for Hamlib to take advantage of
\fBrotctld\fP without the need of rewriting application code. An application
user can select rotor model 2 ("NET rotctl") and then set rot_pathname to
"localhost:4533" or other network host:port.
.PP
\fBExtended Response Protocol\fP
.PP
An \fIEXPERIMENTAL\fP Extended Response protocol has been introduced into
\fBrotctld\fP as of February 10, 2010. This protocol adds several rules to the
strings returned by \fBrotctld\fP.
.PP
1. The command received by \fBrotctld\fP is echoed with its long command name
followed by the value(s) (if any) received from the client terminated by the
specified response separator as the first record of the block.
.PP
2. The last record of each block is the string "RPTR \fIx\fP\\n" where \fIx\fP is
the numeric return value of the Hamlib backend function that was called by the
command.
.PP
3. Any records consisting of data values returned by the rotor backend are
prepended by a string immediately followed by a colon then a space and then the
value terminated by the response separator. e.g. "Azimuth: 90.000000\\n" when the
command was prepended by '+'.
.PP
4. All commands received will be acknowledged by \fBrotctld\fP with records from
rules 1 and 2. Records from rule 3 are only returned when data values must be
returned to the client.
.PP
An example response to a \fI+P\fP command (note the prepended '+'):
.br
set_pos: 90 45
.br
RPRT 0
.PP
In this case the long command name and values are returned on the first line and
the second line contains the end of block marker and the numeric rig backend
return value indicating success.
.PP
An example response to a \fI+\\get_pos\fP query:
.br
get_pos:
.br
Azimuth: 90.000000
.br
Elevation: 45.000000
.br
RPRT 0
.PP
In this case, as no value is passed to \fBrotctld\fP, the first line consists
only of the long command name. The final line shows that the command was
processed successfully by the rotor backend.
.PP
Invoking the Extended Response protocol requires prepending a command with a
punctuation character. As shown in the examples above, prepending a '+'
character to the command results in the responses being separated by a newline
character ('\\n'). Any other punctuation character recognized by the C
\fIispunct()\fP function except '\\', '?', or '_' will cause that character to
become the response separator and the entire response will be on one line.
.PP
For example, invoking a \fI;\\get_pos\fP query with a leading ';' returns:
.sp
get_pos:;Azimuth: 90.000000;Elevation: 45.000000;RPRT 0
.sp
Or, using the pipe character '|' returns:
.sp
get_pos:|Azimuth: 90.000000|Elevation: 45.000000|RPRT 0
.sp
And a \\set_pos command prepended with a '|' returns:
.sp
set_pos: 135 22.5|RPRT 0
.PP
Such a format will allow reading a response as a single event using a prefered
response separator. Other punctuation characters have not been tested!
.PP
All commands with the exception of \fI\\set_conf\fP have been tested with the
Extended Response protocol and the included \fBtestrotctld.pl\fP script.
.PP
.SH EXAMPLES
Start \fBrotctld\fP for a Ham IV rotor with the RotorEZ installed using a
USB-to-serial adapter and backgrounding:
.sp
$ rotctld -m 401 -r /dev/ttyUSB1 &
.sp
Connect to the already running \fBrotctld\fP, and set position to
135.0 degrees azimuth and 30.0 degrees elevation with a 1 second read timeout:
.sp
$ echo "\\set_pos 135.0 30.0" | nc -w 1 localhost 4533
.sp
Connect to a running \fBrotctld\fP with \fBrotctl\fP on the local host:
.sp
$ rotctl -m2
.SH DIAGNOSTICS
The \fB-v\fP, \fB--version\fP option allows different levels of diagnostics
to be output to \fBstderr\fP and correspond to -v for BUG, -vv for ERR,
-vvv for WARN, -vvvv for VERBOSE, or -vvvvv for TRACE.
to be output to \fBstderr\fP and correspond to -v for BUG, -vv for ERR,
-vvv for WARN, -vvvv for VERBOSE, or -vvvvv for TRACE.
.PP
A given verbose level is useful for providing needed debugging information to
the email address below. For example, TRACE output shows all of the values
sent to and received from the rotator which is very useful for rotator backend
library development and may be requested by the developers.
library development and may be requested by the developers. See the
\fBREADME.betatester\fP and \fBREADME.developer\fP files for more information.
.SH SECURITY
No authentication whatsoever; DO NOT leave this TCP port open wide to the Internet.
Please ask if stronger security is needed.
No authentication whatsoever; DO NOT leave this TCP port open wide to the
Internet. Please ask if stronger security is needed or consider using an
SSH tunnel.
.SH BUGS
The daemon is not detaching and backgrounding itself.
.PP
Much testing needs to be done.
.SH REPORTING BUGS
Report bugs to <hamlib-developer@lists.sourceforge.net>.
.br
.PP
We are already aware of the bugs in the previous section :-)
.SH AUTHORS
Written by Stephane Fillod and the Hamlib Group
Written by Stephane Fillod, Nate Bargmann, and the Hamlib Group
.br
<http://www.hamlib.org>.
.SH COPYRIGHT
Copyright \(co 2000-2009 Stephane Fillod and the Hamlib Group.
Copyright \(co 2000-2009 Stephane Fillod
.br
Copyright \(co 2010 Nate Bargmann
.br
Copyright \(co 2000-2009 the Hamlib Group.
.PP
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY

Wyświetl plik

@ -4,23 +4,23 @@
* This program test/control a rotator using Hamlib.
* It takes commands from network connection.
*
* $Id: rotctld.c,v 1.7 2009-01-04 14:49:17 fillods Exp $
* $Id: rotctld.c,v 1.7 2009-01-04 14:49:17 fillods Exp $
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*
*/
#ifdef HAVE_CONFIG_H
@ -71,26 +71,27 @@ void * handle_socket(void * arg);
void usage();
/*
* Reminder: when adding long options,
* Reminder: when adding long options,
* keep up to date SHORT_OPTIONS, usage()'s output and man page. thanks.
* NB: do NOT use -W since it's reserved by POSIX.
* TODO: add an option to read from a file
*/
#define SHORT_OPTIONS "m:r:s:C:t:T:LevhVl"
#define SHORT_OPTIONS "m:r:s:C:t:T:LuevhVl"
static struct option long_options[] =
{
{"model", 1, 0, 'm'},
{"rot-file", 1, 0, 'r'},
{"serial-speed", 1, 0, 's'},
{"port", 1, 0, 't'},
{"listen-addr", 1, 0, 'T'},
{"list", 0, 0, 'l'},
{"set-conf", 1, 0, 'C'},
{"show-conf",0, 0, 'L'},
{"end-marker", 0, 0, 'e'},
{"verbose", 0, 0, 'v'},
{"help", 0, 0, 'h'},
{"version", 0, 0, 'V'},
{"model", 1, 0, 'm'},
{"rot-file", 1, 0, 'r'},
{"serial-speed",1, 0, 's'},
{"port", 1, 0, 't'},
{"listen-addr", 1, 0, 'T'},
{"list", 0, 0, 'l'},
{"set-conf", 1, 0, 'C'},
{"show-conf", 0, 0, 'L'},
{"dump-caps", 0, 0, 'u'},
{"end-marker", 0, 0, 'e'},
{"verbose", 0, 0, 'v'},
{"help", 0, 0, 'h'},
{"version", 0, 0, 'V'},
{0, 0, 0, 0}
};
@ -107,7 +108,7 @@ char send_cmd_term = '\r'; /* send_cmd termination char */
int main (int argc, char *argv[])
{
{
ROT *my_rot; /* handle to rot (instance) */
rot_model_t my_model = ROT_MODEL_DUMMY;
@ -115,6 +116,7 @@ int main (int argc, char *argv[])
int verbose = 0;
int show_conf = 0;
int dump_caps_opt = 0;
const char *rot_file=NULL;
int serial_rate = 0;
char conf_parms[MAXCONFLEN] = "";
@ -135,79 +137,81 @@ int main (int argc, char *argv[])
switch(c) {
case 'h':
usage();
exit(0);
usage();
exit(0);
case 'V':
version();
exit(0);
version();
exit(0);
case 'm':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
my_model = atoi(optarg);
break;
case 'r':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
rot_file = optarg;
break;
case 's':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
serial_rate = atoi(optarg);
break;
case 'C':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
if (*conf_parms != '\0')
strcat(conf_parms, ",");
strncat(conf_parms, optarg, MAXCONFLEN-strlen(conf_parms));
break;
case 't':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
portno = atoi(optarg);
break;
case 'T':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
if (4 != sscanf(optarg, "%d.%d.%d.%d", &a0,&a1,&a2,&a3)) {
usage(); /* wrong arg count */
exit(1);
}
src_addr = (a0<<24)|(a1<<16)|(a2<<8)|a3;
break;
case 'v':
verbose++;
break;
case 'L':
show_conf++;
break;
case 'l':
list_models();
exit(0);
case 'e':
opt_end = 1;
break;
default:
usage(); /* unknown option? */
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
my_model = atoi(optarg);
break;
case 'r':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
rot_file = optarg;
break;
case 's':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
serial_rate = atoi(optarg);
break;
case 'C':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
if (*conf_parms != '\0')
strcat(conf_parms, ",");
strncat(conf_parms, optarg, MAXCONFLEN-strlen(conf_parms));
break;
case 't':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
portno = atoi(optarg);
break;
case 'T':
if (!optarg) {
usage(); /* wrong arg count */
exit(1);
}
if (4 != sscanf(optarg, "%d.%d.%d.%d", &a0,&a1,&a2,&a3)) {
usage(); /* wrong arg count */
exit(1);
}
src_addr = (a0<<24)|(a1<<16)|(a2<<8)|a3;
break;
case 'v':
verbose++;
break;
case 'L':
show_conf++;
break;
case 'l':
list_models();
exit(0);
case 'u':
dump_caps_opt++;
break;
case 'e':
opt_end = 1;
fprintf(stderr, "-e|--end-marker option is deprecated!\nPlease consider using the Extended Response protocol instead.\n");
break;
default:
usage(); /* unknown option? */
exit(1);
}
}
rig_set_debug(verbose<2 ? RIG_DEBUG_WARN: verbose);
rig_debug(RIG_DEBUG_VERBOSE, "rotctld, %s\n", hamlib_version);
rig_debug(RIG_DEBUG_VERBOSE, "Report bugs to "
"<hamlib-developer@lists.sourceforge.net>\n\n");
@ -215,7 +219,7 @@ int main (int argc, char *argv[])
my_rot = rot_init(my_model);
if (!my_rot) {
fprintf(stderr, "Unknown rot num %d, or initialization error.\n",
fprintf(stderr, "Unknown rot num %d, or initialization error.\n",
my_model);
fprintf(stderr, "Please check with --list option.\n");
exit(2);
@ -241,6 +245,16 @@ int main (int argc, char *argv[])
rot_token_foreach(my_rot, print_conf_list, (rig_ptr_t)my_rot);
}
/*
* print out conf parameters, and exits immediately
* We may be interested only in only caps, and rig_open may fail.
*/
if (dump_caps_opt) {
dumpcaps_rot(my_rot, stdout);
rig_cleanup(my_rot); /* if you care about memory */
exit(0);
}
retcode = rot_open(my_rot);
if (retcode != RIG_OK) {
fprintf(stderr,"rot_open: error = %s \n", rigerror(retcode));
@ -257,7 +271,7 @@ int main (int argc, char *argv[])
/*
* Prepare listening socket
*/
sock_listen = socket(AF_INET, SOCK_STREAM, 0);
sock_listen = socket(AF_INET, SOCK_STREAM, 0);
if (sock_listen < 0) {
perror("ERROR opening socket");
exit(1);
@ -403,7 +417,8 @@ void usage()
" -C, --set-conf=PARM=VAL set config parameters\n"
" -L, --show-conf list all config parameters\n"
" -l, --list list all model numbers and exit\n"
" -e, --end-marker use END marker in rotctld protocol\n"
" -u, --dump-caps dump capabilities and exit\n"
" -e, --end-marker use END marker in rotctld protocol (obsolete)\n"
" -v, --verbose set verbose mode, cumulative\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n\n",

Wyświetl plik

@ -0,0 +1,678 @@
#! /usr/bin/perl
# testrotctld.pl - (C) 2008,2010 Nate Bargmann, n0nb@arrl.net
# A Perl test script for the rotctld program.
#
# $Id$
#
# It connects to the rotctld TCP port (default 4533) and queries the daemon
# for some common rot information and sets some values. It also aims to
# provide a bit of example code for Perl scripting.
#
# This program utilizes the Extended Response protocol of rotctld in line
# response mode. See the rotctld(8) man page for details.
#############################################################################
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# See the file 'COPYING' in the main Hamlib distribution directory for the
# complete text of the GNU Public License version 2.
#
#############################################################################
# Perl modules this script uses
use warnings;
use strict;
use IO::Socket;
use Getopt::Long;
use Pod::Usage;
# Global variables
my $socket;
my $host = 'localhost';
my $port = 4533;
my $vfo = '';
my %rot_state = (); # State of the rotor--position, etc.
my %rot_caps = (); # Rotor capabilities from \dump_caps
my $man = 0;
my $help = 0;
my $debug = 0;
my $user_in;
my $ret_val;
# Error values returned from rotctld by Hamlib name
my %errstr = (
RIG_OK => "0", # No error, operation completed sucessfully
RIG_EINVAL => "-1", # invalid parameter
RIG_ECONF => "-2", # invalid configuration (serial,..)
RIG_ENOMEM => "-3", # memory shortage
RIG_ENIMPL => "-4", # function not implemented, but will be
RIG_ETIMEOUT => "-5", # communication timed out
RIG_EIO => "-6", # IO error, including open failed
RIG_EINTERNAL => "-7", # Internal Hamlib error, huh?!
RIG_EPROTO => "-8", # Protocol error
RIG_ERJCTED => "-9", # Command rejected by the rot
RIG_ETRUNC => "-10", # Command performed, but arg truncated
RIG_ENAVAIL => "-11", # function not available
RIG_ENTARGET => "-12", # VFO not targetable
RIG_BUSERROR => "-13", # Error talking on the bus
RIG_BUSBUSY => "-14", # Collision on the bus
RIG_EARG => "-15", # NULL RIG handle or any invalid pointer parameter in get arg
RIG_EVFO => "-16", # Invalid VFO
RIG_EDOM => "-17", # Argument out of domain of func
# testctld specific error values from -100 onward
CTLD_OK => "-100", # testrotctld -- No error
CTLD_ENIMPL => "-103", # testrotctld -- %rot_caps reports backend function not implemented
CTLD_EPROTO => "-108", # testrotctld -- Echoed command mismatch or other error
);
# Error values returned from rotctld by Hamlib value
my %errval = reverse %errstr;
# Rotor '\move' command token values
my %direct = (
UP => '2',
DOWN => '4',
LEFT => '8',
CCW => '8', # Synonym for LEFT
RIGHT => '16',
CW => '16', # Synonym for RIGHT
);
#############################################################################
# Main program
#
#############################################################################
# Parse command line options
argv_opts();
# Create the new socket.
# 'localhost' may be replaced by any hostname or IP address where a
# rotctld instance is running.
# Timeout is set to 5 seconds.
$socket = new IO::Socket::INET (PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => 5 )
or die $@;
print "Welcome to testrotctld.pl a program to test `rotctld'\n";
print "Type '?' or 'help' for commands help.\n\n";
# Populate %rot_caps from \dump_caps
$ret_val = dump_caps();
# Tell user what rotor rotctld is working with
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Hamlib Model: " . $rot_caps{'Caps dump for model'} . "\t";
print "Common Name: " . $rot_caps{'Mfg name'} . ' ' . $rot_caps{'Model name'} . "\n\n\n";
} else {
errmsg ($ret_val);
}
# Interactive loop
do {
print "rotctld command: ";
chomp($user_in = <>);
# P, \set_pos
if ($user_in =~ /^(P|\\set_pos)\s+([-+]?([0-9]*\.)?[0-9]+)\s+([-+]?([0-9]*\.)?[0-9]+)\b$/) {
if ($rot_caps{'Can set Position'} eq 'Y') {
# Get the entered az and el values
print "Az = $2, El = $4\n" if $debug;
$ret_val = rot_cmd('set_pos', $2, $4);
unless ($ret_val eq $errstr{'RIG_OK'}) {
errmsg ($ret_val);
}
} else {
errmsg($errstr{'CTLD_ENIMPL'});
}
}
# p, \get_pos
elsif ($user_in =~ /^(p|\\get_pos)\b$/) {
if ($rot_caps{'Can get Position'} eq 'Y') {
# Query rot and process result
$ret_val = rot_cmd('get_pos');
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Azimuth: " . $rot_state{Azimuth} . "\n";
print "Elevation: " . $rot_state{Elevation} . "\n\n";
} else {
errmsg ($ret_val);
}
} else {
errmsg($errstr{'CTLD_ENIMPL'});
}
}
# M, \move
elsif ($user_in =~ /^(M|\\move)\s+([A-Z]+)\s+(\d+)\b$/) {
if ($rot_caps{'Can Move'} eq 'Y') {
# Get the entered mode and passband values
print "Move = $direct{$2}, Speed = $3\n" if $debug;
$ret_val = rot_cmd('move', $direct{$2}, $3);
unless ($ret_val eq $errstr{'RIG_OK'}) {
errmsg ($ret_val);
}
} else {
errmsg($errstr{'CTLD_ENIMPL'});
}
}
# S, \stop
elsif ($user_in =~ /^(S|\\stop)\b$/) {
if ($rot_caps{'Can Stop'} eq 'Y') {
print "Stop\n" if $debug;
$ret_val = rot_cmd('stop'); # $vfo not used!
unless ($ret_val eq $errstr{'RIG_OK'}) {
errmsg ($ret_val);
}
} else {
errmsg($errstr{'CTLD_ENIMPL'});
}
}
# K, \park
elsif ($user_in =~ /^(K|\\park)\b$/) {
if ($rot_caps{'Can Park'} eq 'Y') {
print "Park\n" if $debug;
$ret_val = rot_cmd('park');
unless ($ret_val eq $errstr{'RIG_OK'}) {
errmsg ($ret_val);
}
} else {
errmsg($errstr{'CTLD_ENIMPL'});
}
}
# R, \reset
elsif ($user_in =~ /^(R|\\reset)\s+(\d)\b$/) {
if ($rot_caps{'Can Reset'} eq 'Y') {
print "Reset\n" if $debug;
$ret_val = rot_cmd('reset', $2);
unless ($ret_val eq $errstr{'RIG_OK'}) {
errmsg ($ret_val);
}
} else {
errmsg($errstr{'CTLD_ENIMPL'});
}
}
# _, \get_info
elsif ($user_in =~ /^(_|\\get_info)\b$/) {
if ($rot_caps{'Can get Info'} eq 'Y') {
print "Get info\n" if $debug;
$ret_val = rot_cmd('get_info');
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Info: " . $rot_state{Info} . "\n\n";
} else {
errmsg ($ret_val);
}
} else {
errmsg($errstr{'CTLD_ENIMPL'});
}
}
# L, \lonlat2loc
elsif ($user_in =~ /^(L|\\lonlat2loc)\s+([-+]?([0-9]*\.)?[0-9]+)\s+([-+]?([0-9]*\.)?[0-9]+)\s+(\d+)\b$/) {
print "Longitude = $2, Latitude = $4, Length = $6\n" if $debug;
$ret_val = rot_cmd('lonlat2loc', $2, $4, $6);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Locator: " . $rot_state{Locator} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# l, \loc2lonlat
elsif ($user_in =~ /^(l|\\loc2lonlat)\s+([A-Za-z0-9]+)\b$/) {
print "Locator = $2\n" if $debug;
$ret_val = rot_cmd('loc2lonlat', $2);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Longitude: " . $rot_state{Longitude} . "\n";
print "Latitude: " . $rot_state{Latitude} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# D, \dms2dec
elsif ($user_in =~ /^(D|\\dms2dec)\s+[+-]?(\d+)\s+(\d+)\s+(([0-9]*\.)?[0-9]+)\s+(\d)\b$/) {
print "Degrees = $2, Minutes = $3, Seconds = $4, S/W = $6\n" if $debug;
$ret_val = rot_cmd('dms2dec', $2, $3, $4, $6);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Decimal Degrees: " . $rot_state{'Dec Degrees'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# d, \dec2dms
elsif ($user_in =~ /^(d|\\dec2dms)\s+([-+]?([0-9]*\.)?[0-9]+)\b$/) {
print "Decimal Degrees = $2\n" if $debug;
$ret_val = rot_cmd('dec2dms', $2);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Degrees: " . $rot_state{Degrees} . "\n";
print "Minutes: " . $rot_state{Minutes} . "\n";
print "Seconds: " . $rot_state{Seconds} . "\n";
print "South/West: " . $rot_state{'S/W'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# E, \dmmm2dec
elsif ($user_in =~ /^(E|\\dmmm2dec)\s+[+-]?(\d+)\s+(([0-9]*\.)?[0-9]+)\s+(\d)\b$/) {
print "Degrees = $2, Minutes = $3, S/W = $5\n" if $debug;
$ret_val = rot_cmd('dmmm2dec', $2, $3, $5);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Decimal Degrees: " . $rot_state{'Dec Deg'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# e, \dec2dmmm
elsif ($user_in =~ /^(e|\\dec2dmmm)\s+([-+]?([0-9]*\.)?[0-9]+)\b$/) {
print "Decimal Degrees = $2\n" if $debug;
$ret_val = rot_cmd('dec2dmmm', $2);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Degrees: " . $rot_state{Degrees} . "\n";
print "Decimal Minutes: " . $rot_state{'Dec Minutes'} . "\n";
print "South/West: " . $rot_state{'S/W'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# B, \qrb
elsif ($user_in =~ /^(B|\\qrb)\s+([-+]?([0-9]*\.)?[0-9]+)\s+([-+]?([0-9]*\.)?[0-9]+)\s+([-+]?([0-9]*\.)?[0-9]+)\s+([-+]?([0-9]*\.)?[0-9]+)\b$/) {
print "Lon 1 = $2, Lat 1 = $4, Lon 2 = $6, Lat 2 = $8\n" if $debug;
$ret_val = rot_cmd('qrb', $2, $4, $6, $8);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Distance (km): " . $rot_state{'QRB Distance'} . "\n";
print "Azimuth (Deg): " . $rot_state{'QRB Azimuth'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# A, \a_sp2a_lp
elsif ($user_in =~ /^(A|\\a_sp2a_lp)\s+([-+]?([0-9]*\.)?[0-9]+)\b$/) {
print "Short Path Degrees = $2\n" if $debug;
$ret_val = rot_cmd('a_sp2a_lp', $2);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Long Path Degrees: " . $rot_state{'Long Path Deg'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# a, \d_sp2d_lp
elsif ($user_in =~ /^(a|\\d_sp2d_lp)\s+([-+]?([0-9]*\.)?[0-9]+)\b$/) {
print "Short Path km = $2\n" if $debug;
$ret_val = rot_cmd('d_sp2d_lp', $2);
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Long Path km: " . $rot_state{'Long Path km'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# 1, \dump_caps
elsif ($user_in =~ /^(1|\\dump_caps)\b$/) {
$ret_val = dump_caps();
if ($ret_val eq $errstr{'RIG_OK'}) {
print "Model: " . $rot_caps{'Caps dump for model'} . "\n";
print "Manufacturer: " . $rot_caps{'Mfg name'} . "\n";
print "Name: " . $rot_caps{'Model name'} . "\n\n";
} else {
errmsg ($ret_val);
}
}
# ?, help
elsif ($user_in =~ /^\?|^help\b$/) {
print <<EOF;
Commands are entered in the same format as described in the rotctld(8)
man page. e.g. generally lower case letters call \\get commands and upper
case letters call \\set commands or long command names may be used. An
exception are the locator commands where paired conversions are arbitrarily
assigned upper and lower case commands.
Values passed to set commands are separated by a single space:
P 150.75 22.5
\\get_pos
See `man rotctld' for complete command descriptions.
Type 'q' or 'exit' to exit $0.
EOF
}
elsif ($user_in !~ /^(exit|q)\b$/i) {
print "Unrecognized command. Type '?' or 'help' for command help.\n"
}
} while ($user_in !~ /^(exit|q)\b$/i);
# Close the connection before we exit.
close($socket);
#############################################################################
# Subroutines for interacting with rotctld
#
#############################################################################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# rot_cmd -- Build command string, check returned data, and populate %rot_state
#
# Passed parameters:
# $cmd = rotctld command
# $p1 - $p4 = \set command parameters
#
# Returns error value from rotctld or local error value if echoed command mismatch
#
sub rot_cmd {
my ($errno, @answer);
my $cmd = shift @_;
my $p1 = shift @_;
my $p2 = shift @_;
my $p3 = shift @_;
my $p4 = shift @_;
# Add a space to the beginning of the $p? arguments
if (defined $p1) {
# "Stringify" parameter value then add a space to the beginning of the string
$p1 .= '';
$p1 = sprintf("%*s", 1 + length $p1, $p1);
} else { $p1 = ''; }
if (defined $p2) {
$p2 .= '';
$p2 = sprintf("%*s", 1 + length $p2, $p2);
} else { $p2 = ''; }
if (defined $p3) {
$p3 .= '';
$p3 = sprintf("%*s", 1 + length $p3, $p3);
} else { $p3 = ''; }
if (defined $p4) {
$p4 .= '';
$p4 = sprintf("%*s", 1 + length $p4, $p4);
} else { $p4 = ''; }
print 'Command: +\\' . $cmd . $p1 . $p2 . $p3 . $p4 . "\n\n" if $debug;
# N.B. Terminate query commands with a newline, e.g. "\n" character.
# N.B. Preceding '+' char to request block or extended response protocol
print $socket '+\\' . $cmd . $p1 . $p2 . $p3 . $p4 . "\n";
# rotctld echoes the command plus value(s) on "get" along with
# separate lines for the queried value(s) and the Hamlib return value.
@answer = get_rotctl($socket);
if ((shift @answer) =~ /^$cmd:/) {
$errno = get_errno(pop @answer);
if ($errno eq $errstr{'RIG_OK'}) {
# At this point the first line of @answer which is the command string echo
# and the last line which is the ending block marker and the Hamlib rot
# function return value have been removed from the array. What is left
# over will be stored in the %state hash as a key: value pair for each
# returned line.
if (@answer) { get_state(@answer) } # Empty array on \set commands
}
return $errno;
} else {
return $errstr{'CTLD_EPROTO'};
}
}
# Get the rot capabilities from Hamlib and store in the %rot_caps hash.
sub dump_caps {
my ($cmd, $errno, @answer);
print $socket "+\\dump_caps\n";
@answer = get_rotctl($socket);
$cmd = shift @answer;
if ($cmd =~ /^dump_caps:/) {
$errno = get_errno(pop @answer);
if ($errno eq $errstr{'RIG_OK'}) {
get_caps(@answer);
}
return $errno;
} else {
return $errstr{'RIG_EPROTO'};
}
}
#############################################################################
# testrotctld.pl helper functions
#
#############################################################################
# Thanks to Uri Guttman on comp.lang.perl.misc for this function.
# 'RPRT' is the token returned by rotctld to mark the end of the reply block.
sub get_rotctl {
my $sock = shift @_;
my @lines;
while (<$sock>) {
# rotctld terminates each line with '\n'
chomp;
push @lines, $_;
return @lines if $_ =~ /^RPRT/;
}
}
# Builds the %rot_state hash from the lines returned by rotctld which are of the
# form "Azimuth: 90.000000, elevation: 45.000000", etc.
sub get_state {
my ($key, $val);
foreach (@_) {
($key, $val) = split(/: /, $_);
$rot_state{$key} = $val;
}
}
# Parse the (large) \dump_caps command response into %rot_caps.
# TODO: process all lines of output
sub get_caps {
my ($key, $val);
foreach (@_) {
if (($_ =~ /^Caps .*:/) or
($_ =~ /^Model .*:/) or
($_ =~ /^Mfg .*:/) or
($_ =~ /^Can .*:/)
) {
($key, $val) = split(/:\s+/, $_);
$rot_caps{$key} = $val;
}
}
}
# Extract the Hamlib error value returned with the last line from rotctld
sub get_errno {
chomp @_;
my @errno = split(/ /, $_[0]);
return $errno[1];
}
# FIXME: Better argument handling
sub errmsg {
unless (($_[0] eq $errstr{'CTLD_EPROTO'}) or ($_[0] eq $errstr{'CTLD_ENIMPL'})) {
print "rotctld returned Hamlib $errval{$_[0]}\n\n";
}
elsif ($_[0] eq $errstr{'CTLD_EPROTO'}) {
print "Echoed command mismatch\n\n";
}
elsif ($_[0] eq $errstr{'CTLD_ENIMPL'}) {
print "Function not yet implemented in Hamlib rot backend\n\n";
}
}
# Parse the command line for supported options. Print help text as needed.
sub argv_opts {
# Parse options and print usage if there is a syntax error,
# or if usage was explicitly requested.
GetOptions('help|?' => \$help,
man => \$man,
"port=i" => \$port,
"host=s" => \$host,
debug => \$debug
) or pod2usage(2);
pod2usage(1) if $help;
pod2usage(-verbose => 2) if $man;
}
# POD for pod2usage
__END__
=head1 NAME
testctld.pl - A test and example program for `rotctld' written in Perl.
=head1 SYNOPSIS
testctld.pl [options]
Options:
--host Hostname or IP address of target `rotctld' process
--port TCP Port of target `rotctld' process
--help Brief help message
--man Full documentation
--debug Enable debugging output
=head1 DESCRIPTION
B<testcld.pl> provides a set of functions to interactively test the Hamlib
I<rotctld> TCP/IP network daemon. It also aims to be an example of programming
code to control a rotor via TCP/IP in Hamlib.
=head1 OPTIONS
=over 8
=item B<--host>
Hostname or IP address of the target I<rotctld> process. Default is I<localhost>
which should resolve to 127.0.0.1 if I</etc/hosts> is configured correctly.
=item B<--port>
TCP port of the target I<rotctld> process. Default is 4533. Mutliple instances
of I<rotctld> will require unique port numbers.
=item B<--help>
Prints a brief help message and exits.
=item B<--man>
Prints this manual page and exits.
=item B<--debug>
Enables debugging output to the console.
=back
=head1 COMMANDS
Commands are the same as described in the rotctld(8) man page. This is only
a brief summary.
F, \set_freq Set frequency in Hz
f, \get_freq Get frequency in Hz
M, \set_mode Set mode including passband in Hz
m, \get_mode Get mode including passband in Hz
V, \set_vfo Set VFO (VFOA, VFOB, etc.)
v, \get_vfo Get VFO (VFOA, VFOB, etc.)
J, \set_rit Set RIT in +/-Hz, '0' to clear
j, \get_rit Get RIT in +/-Hz, '0' indicates Off
Z, \set_xit Set XIT in +/-Hz, '0' to clear
z, \get_rit Get XIT in +/-Hz, '0' indicates Off
T, \set_ptt Set PTT, '1' On, '0' Off
t, \get_ptt Get PTT, '1' indicates On, '0' indicates Off
S, \set_split_vfo Set rot into "split" VFO mode, '1' On, '0' Off
s, \get_split_vfo Get status of :split" VFO mode, '1' On, '0' Off
I, \set_split_freq Set TX VFO frequency in Hz
i, \get_split_freq Get TX VFO frequency in Hz
X, \set_split_mode Set TX VFO mode including passband in Hz
x, \get_split_mode Get TX VFO mode including passband in Hz
2, \power2mW Translate a power value [0.0..1.0] to milliWatts
4, \mW2power Translate milliWatts to a power value [0.0..1.0]
1, \dump_caps Get the rot capabilities and display select values.
=cut