diff --git a/tests/Makefile.am b/tests/Makefile.am index cf23d5a10..fc8bad142 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 diff --git a/tests/rotctl.c b/tests/rotctl.c index a966afb8d..404282b1a 100644 --- a/tests/rotctl.c +++ b/tests/rotctl.c @@ -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[]) "\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); diff --git a/tests/rotctl_parse.c b/tests/rotctl_parse.c index 117202a84..bd65f40e5 100644 --- a/tests/rotctl_parse.c +++ b/tests/rotctl_parse.c @@ -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; iflags & 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 ; iu.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", °); + 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, °, &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", °); + 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, °, &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; +} + diff --git a/tests/rotctl_parse.h b/tests/rotctl_parse.h index 014906892..459b814bf 100644 --- a/tests/rotctl_parse.h +++ b/tests/rotctl_parse.h @@ -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 #include -/* +/* + * external prototype + */ + +int dumpcaps_rot (ROT *, FILE *); + + +/* * Prototypes */ void usage_rot(FILE *); diff --git a/tests/rotctld.8 b/tests/rotctld.8 index af5abe350..5664436e4 100644 --- a/tests/rotctld.8 +++ b/tests/rotctld.8 @@ -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 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\fP and -.\" \fI\fP escape sequences to invoke bold face and italics, +.\" \fI\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 . -.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 . .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 diff --git a/tests/rotctld.c b/tests/rotctld.c index 076b732c9..230806614 100644 --- a/tests/rotctld.c +++ b/tests/rotctld.c @@ -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 " "\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", diff --git a/tests/testrotctld.pl b/tests/testrotctld.pl new file mode 100755 index 000000000..f62def252 --- /dev/null +++ b/tests/testrotctld.pl @@ -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 <) { + # 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 provides a set of functions to interactively test the Hamlib +I 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 process. Default is I +which should resolve to 127.0.0.1 if I is configured correctly. + +=item B<--port> + +TCP port of the target I process. Default is 4533. Mutliple instances +of I 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