/* * Copyright 2017-2018,2021 by Thomas Early, N7TAE * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include "DVAPDongle.h" CDVAPDongle::CDVAPDongle() : MAX_REPL_CNT(20u) { } CDVAPDongle::~CDVAPDongle() { } bool CDVAPDongle::open_device(const char *device) { if (0 == access(device, R_OK | W_OK)) { if (open_serial(device)) { if (0 == flock(serfd, LOCK_EX | LOCK_NB)) { printf("Device %s is now locked for exclusive use\n", device); return true; } else { close(serfd); serfd = -1; fprintf(stderr, "Device %s is alread locked/used\n", device); } } } return false; } bool CDVAPDongle::Initialize(const char *devpath, const char *serialno, const int frequency, const int offset, int const power, const int squelch) { bool ok = false; if (strlen(devpath)) { // device path is specified in cfg file, try to open it ok = open_device(devpath); if (! ok) { fprintf(stderr, "Device '%s' could not be opened\n", devpath); } } else { // serial number is specified in cfg file, try to find it and open it char device[16]; for (int i = 0; i < 32; i++) { sprintf(device, "/dev/ttyUSB%d", i); printf("Trying device %s...\n", device); if (open_device(device)) { if (get_ser(device, serialno)) { ok = true; } else { close(serfd); serfd = -1; } } if (ok) break; } } ok = ok && get_name(); ok = ok && get_fw(); ok = ok && set_modu(); ok = ok && set_mode(); ok = ok && set_sql(squelch); ok = ok && set_pwr(power); ok = ok && set_off(offset); ok = ok && set_freq(frequency); ok = ok && start_dvap(); if (!ok) { if (serfd != -1) { Stop(); close(serfd); serfd = -1; } } return ok; } REPLY_TYPE CDVAPDongle::GetReply(SDVAP_REGISTER &dr) { dr.header = dr.param.control = 0; unsigned int off = 2; int rc = read_from_dvp(&dr.header, 2); // read the header if (rc == 0) return RT_TIMEOUT; if (rc != 2) return RT_ERR; switch (dr.header) { case 0x5u: case 0x6u: case 0x7u: case 0x8u: case 0xcu: case 0xdu: case 0x10u: case 0x2005u: case 0x2007u: case 0x602fu: case 0xa02fu: case 0xc012u: break; // these are all expected headers default: printf("unknown header=0x%d\n", (unsigned)dr.header); if (syncit()) return RT_ERR; return RT_TIMEOUT; } // read the rest of the register uint16_t len = dr.header & 0x1fff; while (off < len) { uint8_t *ptr = (uint8_t *)&dr; rc = read_from_dvp(ptr + off, len - off); if (rc < 0) return RT_TIMEOUT; if (rc > 0) off += rc; } // okay, now we'll parse the register and return its type switch (dr.header) { case 0x5u: switch (dr.param.control) { case 0x18u: if (dr.param.byte) return RT_START; else return RT_STOP; case 0x28u: if (0x1u==dr.param.ustr[0]) return RT_MODU; break; case 0x80u: return RT_SQL; case 0x2au: if (0x0u==dr.param.ustr[0]) return RT_MODE; break; } break; case 0x6u: switch (dr.param.control) { case 0x138u: return RT_PWR; case 0x400u: return RT_OFF; } break; case 0x7u: if (0x4u==dr.param.control && 0x1u==dr.param.ustr[0]) return RT_FW; break; case 0x8u: if (0x220u==dr.param.control) return RT_FREQ; break; case 0xcu: if (0x230u==dr.param.control) return RT_FREQ_LIMIT; break; case 0xdu: if (0x2u==dr.param.control) return RT_SER; break; case 0x10u: if (0x1u==dr.param.control) return RT_NAME; break; case 0x2005u: if (0x118u==dr.param.control) return RT_PTT; break; case 0x2007u: if (0x90u==dr.param.control) return RT_STS; break; case 0x602fu: return RT_HDR_ACK; case 0xa02fu: return RT_HDR; case 0xc012u: return RT_DAT; } printf("Unrecognized data from dvap: header=%#x control=%#x\n", (unsigned)dr.header, (unsigned)dr.param.control); if (syncit()) return RT_ERR; return RT_TIMEOUT; } bool CDVAPDongle::syncit() { unsigned char data[7]; struct timeval tv; fd_set fds; short cnt = 0; printf("Starting syncing dvap\n"); memset(data, 0x00, 7); dvapreg.header = 0x2007u; dvapreg.param.control = 0x90u; while (memcmp(data, &dvapreg, 4) != 0) { FD_ZERO(&fds); FD_SET(serfd, &fds); tv.tv_sec = 0; tv.tv_usec = 1000; int n = select(serfd + 1, &fds, NULL, NULL, &tv); if (n <= 0) { cnt ++; if (cnt > 100) { printf("syncit() uncessful...stopping\n"); return true; } } else { unsigned char c; n = read_from_dvp(&c, 1); if (n > 0) { data[0] = data[1]; data[1] = data[2]; data[2] = data[3]; data[3] = data[4]; data[4] = data[5]; data[5] = data[6]; data[6] = c; cnt = 0; } } } printf("Stopping syncing dvap\n"); return false; } bool CDVAPDongle::get_ser(const char *dvp, const char *dvap_serial_number) { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x2004u; dvapreg.param.control = 0x2u; int rc = write_to_dvp(&dvapreg, 4); if (rc != 4) { printf("Failed to send request to get dvap serial#\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to receive dvap serial#\n"); return false; } } while (reply != RT_SER); if (0 == strcmp(dvapreg.param.sstr, dvap_serial_number)) { printf("Using %s: %s, because serial number matches your configuration file\n", dvp, dvap_serial_number); return true; } printf("Device %s has serial %s, but does not match your configuration value %s\n", dvp, dvapreg.param.sstr, dvap_serial_number); return false; } bool CDVAPDongle::get_name() { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x2004u; dvapreg.param.control = 0x1u; int rc = write_to_dvp(&dvapreg, 4); if (rc != 4) { printf("Failed to send request to get dvap name\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to receive dvap name\n"); return false; } } while (reply != RT_NAME); if (0x10u!=dvapreg.header || 0x1u!=dvapreg.param.control || strncmp(dvapreg.param.sstr, "DVAP Dongle", 11)) { printf("Failed to receive dvap name, got %s\n", dvapreg.param.sstr); return false; } printf("Device name: %.*s\n", 11, dvapreg.param.sstr); return true; } bool CDVAPDongle::get_fw() { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x2005u; dvapreg.param.control = 0x4u; dvapreg.param.ustr[0] = 0x1u; int rc = write_to_dvp(&dvapreg, 5); if (rc != 5) { printf("Failed to send request to get dvap fw\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to receive dvap fw\n"); return false; } } while (reply != RT_FW); unsigned int ver = dvapreg.param.ustr[1] + 256 * dvapreg.param.ustr[2]; printf("dvap fw ver: %u.%u\n", ver / 100, ver % 100); return true; } bool CDVAPDongle::set_modu() { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x5u; dvapreg.param.control = 0x28u; dvapreg.param.ustr[0] = 0x1u; int rc = write_to_dvp(&dvapreg, 5); if (rc != 5) { printf("Failed to send request to set dvap modulation\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to set dvap modulation\n"); return false; } } while (reply != RT_MODU); return true; } bool CDVAPDongle::set_mode() { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x5u; dvapreg.param.control = 0x2au; dvapreg.param.ustr[0] = 0x0u; int rc = write_to_dvp(&dvapreg, 5); if (rc != 5) { printf("Failed to send request to set dvap mode\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to set dvap mode\n"); return false; } } while (reply != RT_MODE); return true; } bool CDVAPDongle::set_sql(int squelch) { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x5u; dvapreg.param.control = 0x80u; if (squelch < -128) { printf("Squelch setting of %d too small, resetting...\n", squelch); squelch = -128; } else if (squelch > -45) { printf("Squelch setting of %d too large, resetting...\n", squelch); squelch = -45; } dvapreg.param.byte = (int8_t)squelch; int rc = write_to_dvp(&dvapreg, 5); if (rc != 5) { printf("Failed to send request to set dvap sql\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to set dvap sql\n"); return false; } } while (reply != RT_SQL); printf("DVAP squelch is %d dB\n", (int)dvapreg.param.byte); return true; } bool CDVAPDongle::set_pwr(int power) { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x6u; dvapreg.param.control = 0x138u; if (power < -12) { printf("Power setting of %d is too low, resetting...\n", power); power = -12; } else if (power > 10) { printf("Power setting of %d is too high, resetting...\n", power); power = 10; } dvapreg.param.word = (int16_t)power; int rc = write_to_dvp(&dvapreg, 6); if (rc != 6) { printf("Failed to send request to set dvap pwr\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to set dvap pwr\n"); return false; } } while (reply != RT_PWR); printf("DVAP power is %d dB\n", (int)dvapreg.param.word); return true; } bool CDVAPDongle::set_off(int offset) { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x6u; dvapreg.param.control = 0x400u; if (offset < -2000) { printf("Offset of %d is too low, resetting...\n", offset); offset = -2000; } else if (offset > 2000) { printf("Offset of %d is too high, resetting...\n", offset); offset = 2000; } dvapreg.param.word = (int16_t)offset; int rc = write_to_dvp(&dvapreg, 6); if (rc != 6) { printf("Failed to send request to set dvap offset\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to set dvap offset\n"); return false; } } while (reply != RT_OFF); printf("DVAP offset is %d Hz\n", (int)dvapreg.param.word); return true; } bool CDVAPDongle::set_freq(int frequency) { unsigned cnt = 0; REPLY_TYPE reply; // first get the frequency limits dvapreg.header = 0x2004u; dvapreg.param.control = 0x230u; int rc = write_to_dvp(&dvapreg, 4); if (rc != 4) { printf("Failed to send request for frequency limits\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests for dvap frequency limits\n"); return false; } } while (reply != RT_FREQ_LIMIT); printf("DVAP Frequency limits are from %d to %d Hz\n", dvapreg.param.twod[0], dvapreg.param.twod[1]); // okay, now we know the frequency limits, get on with the show... if (frequency < dvapreg.param.twod[0]) { printf("Frequency of %d is too small, resetting...\n", frequency); frequency = dvapreg.param.twod[0]; } else if (frequency > dvapreg.param.twod[1]) { printf("Frequency of %d is too large, resetting...\n", frequency); frequency = dvapreg.param.twod[1]; } cnt = 0; dvapreg.header = 0x8u; dvapreg.param.control = 0x220u; dvapreg.param.dword = frequency; rc = write_to_dvp(&dvapreg, 8); if (rc != 8) { printf("Failed to send request to set dvap frequency\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to set dvap frequency\n"); return false; } } while (reply != RT_FREQ); printf("DVAP frequency is %d Hz\n", dvapreg.param.dword); return true; } bool CDVAPDongle::start_dvap() { unsigned cnt = 0; REPLY_TYPE reply; dvapreg.header = 0x5u; dvapreg.param.control = 0x18u; dvapreg.param.byte = 0x1; int rc = write_to_dvp(&dvapreg, 5); if (rc != 5) { printf("Failed to send request to start the dvap dongle\n"); return false; } do { usleep(5000); reply = GetReply(dvapreg); cnt ++; if (cnt >= MAX_REPL_CNT) { printf("Reached max number of requests to start the dvap dongle\n"); return false; } } while (reply != RT_START); return true; } void CDVAPDongle::SendRegister(SDVAP_REGISTER &dr) { unsigned int len = dr.header & 0x1fff; write_to_dvp(&dr, len); return; } int CDVAPDongle::write_to_dvp(const void *buffer, const unsigned int len) { unsigned int ptr = 0; if (len == 0) return 0; uint8_t *buf = (uint8_t *)buffer; while (ptr < len) { ssize_t n = write(serfd, buf + ptr, len - ptr); if (n < 0) { printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); return -1; } if (n > 0) ptr += n; } return len; } int CDVAPDongle::read_from_dvp(void *buffer, unsigned int len) { unsigned int off = 0; fd_set fds; int n; struct timeval tv; ssize_t temp_len; uint8_t *buf = (uint8_t *)buffer; if (len == 0) return 0; while (off < len) { FD_ZERO(&fds); FD_SET(serfd, &fds); if (off == 0) { tv.tv_sec = 0; tv.tv_usec = 0; n = select(serfd + 1, &fds, NULL, NULL, &tv); if (n == 0) return 0; // nothing to read from the dvap } else n = select(serfd + 1, &fds, NULL, NULL, NULL); if (n < 0) { printf("select error=%d on dvap\n", errno); return -1; } if (n > 0) { temp_len = read(serfd, buf + off, len - off); if (temp_len > 0) off += temp_len; } } return len; } bool CDVAPDongle::open_serial(const char *device) { static termios t; serfd = open(device, O_RDWR | O_NOCTTY | O_NDELAY, 0); if (serfd < 0) { printf("Failed to open device [%s], error=%d, message=%s\n", device, errno, strerror(errno)); return false; } if (isatty(serfd) == 0) { printf("Device %s is not a tty device\n", device); close(serfd); serfd = -1; return false; } if (tcgetattr(serfd, &t) < 0) { printf("tcgetattr failed for %s, error=%d, message-%s\n", device, errno, strerror(errno)); close(serfd); serfd = -1; return false; } t.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); t.c_cflag |= CS8; t.c_oflag &= ~(OPOST); t.c_cc[VMIN] = 0; t.c_cc[VTIME] = 10; cfsetospeed(&t, B230400); cfsetispeed(&t, B230400); if (tcsetattr(serfd, TCSANOW, &t) < 0) { printf("tcsetattr failed for %s, error=%dm message=%s\n", device, errno, strerror(errno)); close(serfd); serfd = -1; return false; } return true; } void CDVAPDongle::Stop() { SDVAP_REGISTER dvap; dvap.header = 0x5u; dvap.param.control = 0x18u; dvap.param.byte = 0; write_to_dvp(&dvap, 5); return; } int CDVAPDongle::KeepAlive() { SDVAP_REGISTER dvap; dvap.header = 0x6003u; dvap.nul = 0x0u; return write_to_dvp(&dvap, 3); }