mirror of
https://github.com/lwvmobile/dsd-fme.git
synced 2026-11-12 08:47:24 -05:00
548 lines
15 KiB
C
548 lines
15 KiB
C
/*-------------------------------------------------------------------------------
|
|
* dsd_rigctl.c
|
|
* Simple RIGCTL Client for DSD (remote control of GQRX, SDR++, etc)
|
|
*
|
|
* Portions from https://github.com/neural75/gqrx-scanner
|
|
*
|
|
* LWVMOBILE
|
|
* 2022-10 DSD-FME Florida Man Edition
|
|
*-----------------------------------------------------------------------------*/
|
|
|
|
#include "dsd.h"
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
|
|
//UDP Specific
|
|
#include <arpa/inet.h>
|
|
|
|
#define BUFSIZE 1024
|
|
#define FREQ_MAX 4096
|
|
#define SAVED_FREQ_MAX 1000
|
|
#define TAG_MAX 100
|
|
|
|
//
|
|
// error - wrapper for perror
|
|
//
|
|
void error(char *msg) {
|
|
perror(msg);
|
|
exit(0);
|
|
}
|
|
|
|
struct sockaddr_in address;
|
|
struct sockaddr_in addressA;
|
|
struct sockaddr_in addressM17;
|
|
|
|
//
|
|
// Connect
|
|
//
|
|
int Connect (char *hostname, int portno)
|
|
{
|
|
int sockfd;
|
|
struct sockaddr_in serveraddr;
|
|
struct hostent *server;
|
|
|
|
|
|
/* socket: create the socket */
|
|
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sockfd < 0)
|
|
{
|
|
fprintf(stderr,"ERROR opening socket\n");
|
|
error("ERROR opening socket");
|
|
}
|
|
|
|
|
|
/* gethostbyname: get the server's DNS entry */
|
|
server = gethostbyname(hostname);
|
|
if (server == NULL) {
|
|
fprintf(stderr,"ERROR, no such host as %s\n", hostname);
|
|
//exit(0);
|
|
return (0); //return 0, check on other end and configure pulse input
|
|
}
|
|
|
|
/* build the server's Internet address */
|
|
bzero((char *) &serveraddr, sizeof(serveraddr));
|
|
serveraddr.sin_family = AF_INET;
|
|
bcopy((char *)server->h_addr,
|
|
(char *)&serveraddr.sin_addr.s_addr, server->h_length);
|
|
serveraddr.sin_port = htons(portno);
|
|
|
|
/* connect: create a connection with the server */
|
|
if (connect(sockfd, (const struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
|
|
{
|
|
fprintf(stderr,"ERROR opening socket\n");
|
|
return (0);
|
|
}
|
|
|
|
return sockfd;
|
|
}
|
|
//
|
|
// Send
|
|
//
|
|
bool Send(int sockfd, char *buf)
|
|
{
|
|
int n;
|
|
|
|
n = write(sockfd, buf, strlen(buf));
|
|
if (n < 0)
|
|
error("ERROR writing to socket");
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Recv
|
|
//
|
|
bool Recv(int sockfd, char *buf)
|
|
{
|
|
int n;
|
|
|
|
n = read(sockfd, buf, BUFSIZE);
|
|
if (n < 0)
|
|
error("ERROR reading from socket");
|
|
buf[n]= '\0';
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// GQRX Protocol
|
|
//
|
|
long int GetCurrentFreq(int sockfd)
|
|
{
|
|
long int freq = 0;
|
|
char buf[BUFSIZE];
|
|
char * ptr;
|
|
char * token;
|
|
|
|
Send(sockfd, "f\n");
|
|
Recv(sockfd, buf);
|
|
|
|
if (strcmp(buf, "RPRT 1") == 0 )
|
|
return freq;
|
|
|
|
token = strtok (buf, "\n");
|
|
freq = strtol (token, &ptr, 10);
|
|
// fprintf (stderr, "\nRIGCTL VFO Freq: [%ld]\n", freq);
|
|
return freq;
|
|
}
|
|
|
|
bool SetFreq(int sockfd, long int freq)
|
|
{
|
|
char buf[BUFSIZE];
|
|
|
|
sprintf (buf, "F %ld\n", freq);
|
|
Send(sockfd, buf);
|
|
Recv(sockfd, buf);
|
|
|
|
if (strcmp(buf, "RPRT 1\n") == 0 ) //sdr++ has a linebreak here, is that in all versions of the protocol?
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SetModulation(int sockfd, int bandwidth)
|
|
{
|
|
char buf[BUFSIZE];
|
|
//the bandwidth is now a user/system based configurable variable
|
|
sprintf (buf, "M NFM %d\n", bandwidth); //SDR++ has changed the token from FM to NFM, even if Ryzerth fixes it later, users may still have an older version
|
|
Send(sockfd, buf);
|
|
Recv(sockfd, buf);
|
|
|
|
//if it fails the first time, send the other token instead
|
|
if (strcmp(buf, "RPRT 1\n") == 0 ) //sdr++ has a linebreak here, is that in all versions of the protocol?
|
|
{
|
|
sprintf (buf, "M FM %d\n", bandwidth); //anything not SDR++
|
|
Send(sockfd, buf);
|
|
Recv(sockfd, buf);
|
|
}
|
|
|
|
if (strcmp(buf, "RPRT 1\n") == 0 )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GetSignalLevel(int sockfd, double *dBFS)
|
|
{
|
|
char buf[BUFSIZE];
|
|
|
|
Send(sockfd, "l\n");
|
|
Recv(sockfd, buf);
|
|
|
|
if (strcmp(buf, "RPRT 1") == 0 )
|
|
return false;
|
|
|
|
sscanf(buf, "%lf", dBFS);
|
|
*dBFS = round((*dBFS) * 10)/10;
|
|
|
|
if (*dBFS == 0.0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool GetSquelchLevel(int sockfd, double *dBFS)
|
|
{
|
|
char buf[BUFSIZE];
|
|
|
|
Send(sockfd, "l SQL\n");
|
|
Recv(sockfd, buf);
|
|
|
|
if (strcmp(buf, "RPRT 1") == 0 )
|
|
return false;
|
|
|
|
sscanf(buf, "%lf", dBFS);
|
|
*dBFS = round((*dBFS) * 10)/10;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SetSquelchLevel(int sockfd, double dBFS)
|
|
{
|
|
char buf[BUFSIZE];
|
|
|
|
sprintf (buf, "L SQL %f\n", dBFS);
|
|
Send(sockfd, buf);
|
|
Recv(sockfd, buf);
|
|
|
|
if (strcmp(buf, "RPRT 1") == 0 )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
//
|
|
// GetSignalLevelEx
|
|
// Get a bunch of sample with some delay and calculate the mean value
|
|
//
|
|
bool GetSignalLevelEx(int sockfd, double *dBFS, int n_samp)
|
|
{
|
|
double temp_level;
|
|
*dBFS = 0;
|
|
int errors = 0;
|
|
for (int i = 0; i < n_samp; i++)
|
|
{
|
|
if ( GetSignalLevel(sockfd, &temp_level) )
|
|
*dBFS = *dBFS + temp_level;
|
|
else
|
|
errors++;
|
|
usleep(1000);
|
|
}
|
|
*dBFS = *dBFS / (n_samp - errors);
|
|
return true;
|
|
}
|
|
|
|
//shoe in UDP input connection here...still having issues that I don't know how to resolve
|
|
int UDPBind (char *hostname, int portno)
|
|
{
|
|
UNUSED(hostname);
|
|
|
|
int sockfd;
|
|
struct sockaddr_in serveraddr;
|
|
|
|
/* socket: create the socket */
|
|
//UDP socket
|
|
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
if (sockfd < 0)
|
|
{
|
|
fprintf(stderr,"ERROR opening UDP socket\n");
|
|
error("ERROR opening UDP socket");
|
|
}
|
|
|
|
/* build the server's Internet address */
|
|
bzero((char *) &serveraddr, sizeof(serveraddr));
|
|
serveraddr.sin_family = AF_INET;
|
|
serveraddr.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY
|
|
serveraddr.sin_port = htons(portno);
|
|
|
|
//Bind socket to listening
|
|
if (bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) {
|
|
perror("ERROR on binding UDP Port");
|
|
}
|
|
|
|
//set these for non blocking when no samples to read
|
|
struct timeval read_timeout;
|
|
read_timeout.tv_sec = 0;
|
|
read_timeout.tv_usec = 10;
|
|
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &read_timeout, sizeof read_timeout);
|
|
|
|
return sockfd;
|
|
}
|
|
|
|
//going to leave this function available, even if completely switched over to rtl_dev_tune now, may be useful in the future
|
|
void rtl_udp_tune(dsd_opts * opts, dsd_state * state, long int frequency)
|
|
{
|
|
UNUSED(state);
|
|
|
|
int handle;
|
|
unsigned short udp_port = opts->rtl_udp_port;
|
|
char data[5] = {0}; //data buffer size is 5 for UDP frequency tuning
|
|
struct sockaddr_in address;
|
|
|
|
uint32_t new_freq = frequency;
|
|
opts->rtlsdr_center_freq = new_freq; //for ncurses terminal display after rtl is started up
|
|
|
|
handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
data[0] = 0;
|
|
data[1] = new_freq & 0xFF;
|
|
data[2] = (new_freq >> 8) & 0xFF;
|
|
data[3] = (new_freq >> 16) & 0xFF;
|
|
data[4] = (new_freq >> 24) & 0xFF;
|
|
|
|
memset((char * ) & address, 0, sizeof(address));
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = inet_addr("127.0.0.1"); //make user configurable later
|
|
address.sin_port = htons(udp_port);
|
|
sendto(handle, data, 5, 0, (const struct sockaddr * ) & address, sizeof(struct sockaddr_in));
|
|
|
|
close (handle); //close socket after sending.
|
|
}
|
|
|
|
int udp_socket_blaster(dsd_opts * opts, dsd_state * state, size_t nsam, void * data)
|
|
{
|
|
UNUSED(state);
|
|
size_t err = 0;
|
|
|
|
//listen with:
|
|
|
|
//short 8k/2
|
|
//socat stdio udp-listen:23456 | play --buffer 320*2 -q -b 16 -r 8000 -c2 -t s16 -
|
|
|
|
//short 8k/1
|
|
//socat stdio udp-listen:23456 | play --buffer 160*2 -q -b 16 -r 48000 -c2 -t s16 -
|
|
|
|
//float 8k/2
|
|
//socat stdio udp-listen:23456 | play --buffer 320*4 -q -e float -b 32 -r 8000 -c2 -t f32 -
|
|
|
|
//float 8k/1
|
|
//socat stdio udp-listen:23456 | play --buffer 160*4 -q -e float -b 32 -r 8000 -c1 -t f32 -
|
|
|
|
//send audio or data to socket
|
|
err = sendto(opts->udp_sockfd, data, nsam, 0, (const struct sockaddr * ) & address, sizeof(struct sockaddr_in));
|
|
if (err < 0) fprintf (stderr, "\n UDP SENDTO ERR %ld", err); //return value here is size_t number of characters sent, or -1 for failure
|
|
if (err < nsam) fprintf (stderr, "\n UDP Underflow %ld", err); //I'm not even sure if this is possible
|
|
}
|
|
|
|
int m17_socket_receiver(dsd_opts * opts, void * data)
|
|
{
|
|
size_t err = 0;
|
|
struct sockaddr_in cliaddr;
|
|
socklen_t len = sizeof(cliaddr);
|
|
|
|
//receive data from socket
|
|
err = recvfrom(opts->udp_sockfd, data, MSG_WAITALL, 0, (struct sockaddr * ) & address, &len);
|
|
|
|
return err;
|
|
}
|
|
|
|
//Analog UDP port on +2 of normal open socket
|
|
int udp_socket_blasterA(dsd_opts * opts, dsd_state * state, size_t nsam, void * data)
|
|
{
|
|
UNUSED(state);
|
|
size_t err = 0;
|
|
|
|
//listen with:
|
|
|
|
//short 48k/1
|
|
//socat stdio udp-listen:23456 | play --buffer 960*2 -q -b 16 -r 48000 -c1 -t s16 -
|
|
|
|
//send audio or data to socket
|
|
err = sendto(opts->udp_sockfdA, data, nsam, 0, (const struct sockaddr * ) & addressA, sizeof(struct sockaddr_in));
|
|
if (err < 0) fprintf (stderr, "\n UDP SENDTO ERR %ld", err); //return value here is size_t number of characters sent, or -1 for failure
|
|
if (err < nsam) fprintf (stderr, "\n UDP Underflow %ld", err); //I'm not even sure if this is possible
|
|
}
|
|
|
|
int m17_socket_blaster(dsd_opts * opts, dsd_state * state, size_t nsam, void * data)
|
|
{
|
|
UNUSED(state);
|
|
unsigned long long int err = 0;
|
|
|
|
//listen with:
|
|
|
|
//XX packed bytes
|
|
//socat stdio udp-listen:17000 | (decoder)
|
|
|
|
//send audio or data to socket
|
|
err = sendto(opts->m17_udp_sock, data, nsam, 0, (const struct sockaddr * ) & addressM17, sizeof(struct sockaddr_in));
|
|
//RETURN Value should be ACKN or NACK, or PING, or PONG
|
|
|
|
return (err);
|
|
}
|
|
|
|
int udp_socket_connect(dsd_opts * opts, dsd_state * state)
|
|
{
|
|
UNUSED(state);
|
|
|
|
long int err = 0;
|
|
err = opts->udp_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP Socket Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
// Don't think this is needed, but doesn't seem to hurt to keep it here either
|
|
int broadcastEnable = 1;
|
|
err = setsockopt(opts->udp_sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP Broadcast Set Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
memset((char * ) & address, 0, sizeof(address));
|
|
address.sin_family = AF_INET;
|
|
err = address.sin_addr.s_addr = inet_addr(opts->udp_hostname);
|
|
if (err < 0) //error in this context reports back 32-bit inet_addr reversed order byte pairs
|
|
{
|
|
fprintf (stderr, " UDP inet_addr Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
address.sin_port = htons(opts->udp_portno);
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP htons Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
}
|
|
|
|
int udp_socket_connectA(dsd_opts * opts, dsd_state * state)
|
|
{
|
|
UNUSED(state);
|
|
|
|
long int err = 0;
|
|
err = opts->udp_sockfdA = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP Socket Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
// Don't think this is needed, but doesn't seem to hurt to keep it here either
|
|
int broadcastEnable = 1;
|
|
err = setsockopt(opts->udp_sockfdA, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP Broadcast Set Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
memset((char * ) & addressA, 0, sizeof(addressA));
|
|
addressA.sin_family = AF_INET;
|
|
err = addressA.sin_addr.s_addr = inet_addr(opts->udp_hostname);
|
|
if (err < 0) //error in this context reports back 32-bit inet_addr reversed order byte pairs
|
|
{
|
|
fprintf (stderr, " UDP inet_addr Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
addressA.sin_port = htons(opts->udp_portno+2); //plus 2 to current port assignment for the analog port value
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP htons Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
}
|
|
|
|
int udp_socket_connectM17(dsd_opts * opts, dsd_state * state)
|
|
{
|
|
UNUSED(state);
|
|
|
|
long int err = 0;
|
|
err = opts->m17_udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP Socket Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
// Don't think this is needed, but doesn't seem to hurt to keep it here either
|
|
int broadcastEnable = 1;
|
|
err = setsockopt(opts->m17_udp_sock, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP Broadcast Set Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
memset((char * ) & addressM17, 0, sizeof(addressM17));
|
|
addressM17.sin_family = AF_INET;
|
|
err = addressM17.sin_addr.s_addr = inet_addr(opts->m17_hostname);
|
|
if (err < 0) //error in this context reports back 32-bit inet_addr reversed order byte pairs
|
|
{
|
|
fprintf (stderr, " UDP inet_addr Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
addressM17.sin_port = htons(opts->m17_portno);
|
|
if (err < 0)
|
|
{
|
|
fprintf (stderr, " UDP htons Error %ld\n", err);
|
|
return (err);
|
|
}
|
|
|
|
}
|
|
|
|
void return_to_cc (dsd_opts * opts, dsd_state * state)
|
|
{
|
|
//extra safeguards due to sync issues with NXDN
|
|
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
|
|
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
|
|
|
|
memset (state->active_channel, 0, sizeof(state->active_channel));
|
|
|
|
//reset dmr blocks
|
|
dmr_reset_blocks (opts, state);
|
|
|
|
//zero out additional items
|
|
state->lasttg = 0;
|
|
state->lasttgR = 0;
|
|
state->lastsrc = 0;
|
|
state->lastsrcR = 0;
|
|
state->payload_algid = 0;
|
|
state->payload_algidR = 0;
|
|
state->payload_keyid = 0;
|
|
state->payload_keyidR = 0;
|
|
state->payload_mi = 0;
|
|
state->payload_miR = 0;
|
|
state->payload_miP = 0;
|
|
state->payload_miN = 0;
|
|
opts->p25_is_tuned = 0;
|
|
state->p25_vc_freq[0] = state->p25_vc_freq[1] = 0;
|
|
|
|
//tune back to the control channel -- NOTE: Doesn't work correctly on EDACS Analog Voice
|
|
//RIGCTL
|
|
if (opts->p25_trunk == 1 && opts->use_rigctl == 1)
|
|
{
|
|
if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw);
|
|
SetFreq(opts->rigctl_sockfd, state->p25_cc_freq);
|
|
}
|
|
|
|
//rtl
|
|
#ifdef USE_RTLSDR
|
|
if (opts->p25_trunk == 1 && opts->audio_in_type == 3) rtl_dev_tune (opts, state->p25_cc_freq);
|
|
#endif
|
|
|
|
state->last_cc_sync_time = time(NULL);
|
|
|
|
//if P25p2 VCH and going back to P25p1 CC, flip symbolrate
|
|
if (state->p25_cc_is_tdma == 0)
|
|
{
|
|
state->samplesPerSymbol = 10;
|
|
state->symbolCenter = 4;
|
|
}
|
|
|
|
//if P25p1 Data Revert on P25p2 TDMA CC, flip symbolrate
|
|
if (state->p25_cc_is_tdma == 1)
|
|
{
|
|
state->samplesPerSymbol = 8;
|
|
state->symbolCenter = 3;
|
|
}
|
|
|
|
// fprintf (stderr, "\n User Activated Return to CC; \n ");
|
|
}
|