/* * Hamlib SatEL backend - main file * Copyright (c) 2021 Joshua Lynch * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "hamlib/rig.h" #include #include #include #include #include #include "hamlib/rotator.h" #include "serial.h" #include "register.h" #include "satel.h" /** * Protocol documentation. * * Apparently, the system is modeled after this one: * “An Inexpensive Az-El Rotator System” * "Dec, 1999, QST article by Jim Koehler, VE5FP * * '?' - returns 'SatEL\r\n'. a good test to see if there's * connectivity. * * 'g' - enable motion. nothing happens without this enabled. * * 'z' - display rotator status. contains current Az/El among other * things. here's an example: * * Motion ENABLED * Mode 0 - azimuth break at NORTH * Time: 2001/00/00 00:00:07 * Azimuth = 000 Absolute = 000 * Elevation = 000 * * Number of stored positions: 00 * * * '*' - reset the rotator controller. * * 'pAZ EL\r\n' - tell the rotator where to point where AZ is the * integer azimuth and EL is the integer * elevation. e.g. 'p010 045\n'. the controller will * report the current pointing status after the * operation has completed. * * NOTE: The SatEL system changed a few commands as described in the * user's manual. They are not used here. You can find the manual for * this rotator here: * * http://www.codeposse.com/~jlynch/SatEL%20Az-EL.pdf * */ /** * Idiosyncrasies * * - the controller does zero input checking. you can put it into an * incredibly bad state very easily. * * - the controller doesn't accept any data whilst moving the * rotators. In fact, you can put the controller into a bad state on * occasion if you try and send it commands while its slewing the * rotators. this means we have a really long read timeout so we can * wait for the rotators to slew around before accepting any more * commands. * */ #define BUF_SIZE 256 typedef struct satel_stat satel_stat_t; struct satel_stat { bool motion_enabled; // int mode; // time_t time; // int absolute; int az; int el; }; static int satel_cmd(ROT *rot, char *cmd, int cmdlen, char *res, int reslen) { int ret; hamlib_port_t *rotp = ROTPORT(rot); rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); rig_flush(rotp); ret = write_block(rotp, (unsigned char *) cmd, cmdlen); if (ret != RIG_OK) { return ret; } if (reslen > 0 && res != NULL) { ret = read_string(rotp, (unsigned char *) res, reslen, "\n", 1, 0, 1); if (ret < 0) { return ret; } } return RIG_OK; } static int satel_read_status(ROT *rot, satel_stat_t *stat) { char resbuf[BUF_SIZE]; char *p; int ret; hamlib_port_t *rotp = ROTPORT(rot); // read motion state ret = read_string(rotp, (unsigned char *) resbuf, BUF_SIZE, "\n", 1, 0, 1); if (ret < 0) { return ret; } stat->motion_enabled = strcmp(resbuf, "Motion ENABLED") == 0 ? true : false; // XXX skip mode ret = read_string(rotp, (unsigned char *) resbuf, BUF_SIZE, "\n", 1, 0, 1); if (ret < 0) { return ret; } // XXX skip time ret = read_string(rotp, (unsigned char *) resbuf, BUF_SIZE, "\n", 1, 0, 1); if (ret < 0) { return ret; } // read azimuth line ret = read_string(rotp, (unsigned char *) resbuf, BUF_SIZE, "\n", 1, 0, 1); if (ret < 0) { return ret; } p = resbuf + 10; p[3] = '\0'; stat->az = (int)strtof(p, NULL); // read elevation line ret = read_string(rotp, (unsigned char *) resbuf, BUF_SIZE, "\n", 1, 0, 1); if (ret < 0) { return ret; } p = resbuf + 12; p[3] = '\0'; stat->el = (int)strtof(p, NULL); // skip blank line ret = read_string(rotp, (unsigned char *) resbuf, BUF_SIZE, "\n", 1, 0, 1); if (ret < 0) { return ret; } // XXX skip stored position count ret = read_string(rotp, (unsigned char *) resbuf, BUF_SIZE, "\n", 1, 0, 1); if (ret < 0) { return ret; } return RIG_OK; } static int satel_get_status(ROT *rot, satel_stat_t *stat) { int ret; ret = satel_cmd(rot, "z", 1, NULL, 0); if (ret != RIG_OK) { return ret; } return satel_read_status(rot, stat); } static int satel_rot_open(ROT *rot) { char resbuf[BUF_SIZE]; int ret; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); // are we connected? ret = satel_cmd(rot, "?", 1, resbuf, BUF_SIZE); if (ret != RIG_OK) { return ret; } ret = strncasecmp("SatEL", resbuf, 5); if (ret != 0) { return -RIG_EIO; } // yep, reset system ret = satel_cmd(rot, "*", 1, NULL, 0); if (ret != RIG_OK) { return ret; } return RIG_OK; } static int satel_rot_set_position(ROT *rot, azimuth_t az, elevation_t el) { char cmdbuf[BUF_SIZE]; int ret; satel_stat_t stat; rig_debug(RIG_DEBUG_VERBOSE, "%s called: %.2f %.2f\n", __func__, az, el); ret = satel_get_status(rot, &stat); if (ret < 0) { return ret; } if (stat.motion_enabled == false) { ret = satel_cmd(rot, "g", 1, NULL, 0); if (ret != RIG_OK) { return ret; } } SNPRINTF(cmdbuf, BUF_SIZE, "p%d %d\r\n", (int)az, (int)el); ret = satel_cmd(rot, cmdbuf, strlen(cmdbuf), NULL, 0); if (ret != RIG_OK) { return ret; } // wait-for, read and discard the status message ret = satel_read_status(rot, &stat); if (ret < 0) { return ret; } return RIG_OK; } static int satel_rot_get_position(ROT *rot, azimuth_t *az, elevation_t *el) { int ret; satel_stat_t stat; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); ret = satel_get_status(rot, &stat); if (ret < 0) { return ret; } *az = stat.az; *el = stat.el; return RIG_OK; } static int satel_rot_stop(ROT *rot) { rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); // send reset command return satel_cmd(rot, "*", 1, NULL, 0); } static const char *satel_rot_get_info(ROT *rot) { rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); return "SatEL rotator"; } /* * Satel rotator capabilities. */ const struct rot_caps satel_rot_caps = { ROT_MODEL(ROT_MODEL_SATEL), .model_name = "SatEL", .mfg_name = "SatEL", .version = "20210123.0", .copyright = "LGPL", .status = RIG_STATUS_STABLE, .rot_type = ROT_TYPE_AZEL, .port_type = RIG_PORT_SERIAL, .serial_rate_max = 9600, .serial_rate_min = 9600, .serial_data_bits = 8, .serial_stop_bits = 1, .serial_parity = RIG_PARITY_NONE, .serial_handshake = RIG_HANDSHAKE_NONE, .write_delay = 0, .post_write_delay = 0, .timeout = 60000, .retry = 0, .min_az = 0., .max_az = 360., .min_el = 0., .max_el = 90., .rot_open = satel_rot_open, .get_position = satel_rot_get_position, .set_position = satel_rot_set_position, .stop = satel_rot_stop, .get_info = satel_rot_get_info, .priv = NULL, /* priv */ }; DECLARE_INITROT_BACKEND(satel) { rig_debug(RIG_DEBUG_VERBOSE, "%s: _init called\n", __func__); rot_register(&satel_rot_caps); return RIG_OK; }