Add first implementation of multicast functions and multicastclient.c test program.

Next step will be to implement this when rig is opened.
This will turn Hamlib into a polling system in addition to polling -- caching will handle repeated requests
This should mean basic functions of freq, mode, split, ptt (and others when implemented) will have almost
immediate responses as only the cached values should be used.
https://github.com/Hamlib/Hamlib/issues/695
pull/1289/head
Mike Black W9MDB 2023-05-07 06:50:35 -05:00
rodzic f94ca77399
commit e3c2a8d6ca
8 zmienionych plików z 603 dodań i 98 usunięć

Wyświetl plik

@ -2,15 +2,15 @@ Works on Windows and Linux
Requires you get dotnet installed of course
For Wiundows install Visual Studio or such to get dotnet
For Windows install Visual Studio or such to get dotnet
On Ubuntu 20.04 it was this
On Ubuntu 21.04 it was this
apt install snap
apt install mono-complete
snap install dotnet-sdk --classic --channel=5.0
snap install dotnet-sdk --classic --channel=6.0
snap alias dotnet-sdk.dotnet dotnet
snap install dotnet-runtime-50 --classic
snap alias dotnet-runtime-50.dotnet dotnet
snap install dotnet-runtime-60 --classic
snap alias dotnet-runtime-60.dotnet dotnet
export DOTNET_ROOT=/snap/dotnet-sdk/current
Once dotnet is OK
@ -19,4 +19,20 @@ dotnet build
You should then be able to run
./bin/Debug/net5.0/multicast
./bin/Debug/net6.0/multicast
======================================================
Waiting for Net 7.0/8.0 to be in Ubunut main packages
Following did not work
sudo apt remove 'dotnet*' 'aspnet*' 'netstandard*'
touch /etc/apt/preferences
// add to preferences
Package: dotnet* aspnet* netstandard*
Pin: origin "archive.ubuntu.com"
Pin-Priority: -10
// end preferences
snap remove dotnet-sdk
snap install dotnet-sdk --classic --channel=7.0
snap alias dotnet-runtime-70.dotnet dotnet

Wyświetl plik

@ -9,6 +9,7 @@ namespace HamlibMultiCast
{
public class HamlibMulticastClient
{
public string __comment1__;
public class VFO
{
public string Name;
@ -17,19 +18,25 @@ namespace HamlibMultiCast
public int Width;
public bool RX;
public bool TX;
public int WidthLower;
public int WidthUpper;
}
public string __comment_spectrum__;
public class SpectrumClass
{
public string Name;
public int Length;
public string __comment_spectrum_data__;
public string Data;
public string Type;
public int MinLevel;
public int MaxLevel;
public int MinStrength;
public int MaxStrength;
public string __comment_spectrum_center__;
public double CenterFreq;
public int Span;
public string __comment_spectrum_fixed__;
public double LowFreq;
public double HighFreq;
}
@ -38,14 +45,26 @@ namespace HamlibMultiCast
public string Command;
public string Status;
}
public string __comment_gps__;
public class GPS {
public double Latitude;
public double Longitude;
public double Altitude;
}
public string ID;
public string VFOCurr;
public List<VFO> VFOs { get; set; }
public bool Split;
public string __comment_time__;
public string Time;
public bool SatMode;
public string Rig;
public string App;
public string __comment_version__;
public string Version;
public string __comment_seq__;
public UInt32 Seq;
public string __comment_crc__;
public string CRC;
public List<SpectrumClass> Spectra { get; set; }
}

Wyświetl plik

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

Wyświetl plik

@ -1,93 +1,16 @@
"ID": "FT-991:/dev/ttyUSB0",
"Time": 2023-05-05T04:32:47.834952-0000,
"Sequence": 892,
"VFOCurr": "VFOA",
"VFOs": [
{
"__comment1__": "customizable rig identification -- will allow multiple rigs to be on the multicast",
"ID": "Rig#1",
"VFOs": [
{
"Name": "VFOA",
"Freq": 14074000,
"Mode": "USB",
"Width": 2800,
"WidthLower": 200,
"WidthUpper": 3000,
"RX": true,
"TX": false
},
{
"Name": "VFOB",
"Freq": 14076000,
"Mode": "USB",
"Width": 2800,
"WidthLower": 200,
"WidthUpper": 3000,
"RX": false,
"TX": true
}],
"__comment_spectrum__": "Rigs that have spectrum output may include this data",
"Spectra": [
{
"Name": "Main",
"Length": 475,
"__comment_spectrum_data__": "2-char hex bytes so data len=2*Length",
"Data": "00AAFF75BD2AAA...",
"Type": "FIXED|CENTER",
"MinLevel": 0,
"MaxLevel": 140,
"MinStrength": -100,
"MaxStrength": 0,
"__comment_spectrum_center__": "If Type=CENTER, the following fields will be present:",
"CenterFreq": 14267000,
"Span": 25000,
"__comment_spectrum_fixed__": "If SpectrumType=FIXED, the following fields will be present:",
"LowFreq": 14000000,
"HighFreq": 14250000
},
{
"Name": "Sub",
"Length": 475,
"__comment_spectrum_data__": "2-char hex bytes so data len=2*Length",
"Data": "00AAFF75BD2AAA...",
"Type": "FIXED|CENTER",
"MinLevel": 0,
"MaxLevel": 140,
"MinStrength": -100,
"MaxStrength": 0,
"__comment_spectrum_center__": "If Type=CENTER, the following fields will be present:",
"CenterFreq": 14267000,
"Span": 25000,
"__comment_spectrum_fixed__": "If SpectrumType=FIXED, the following fields will be present:",
"LowFreq": 14000000,
"HighFreq": 14250000
}],
"LastCommand": {
"ID": "MyApp 123",
"Command": "set_freq VFOA 14074000",
"Status": "OK"
},
"__comment_gps__": "Rigs that have gps output may include this data Lat -180 to 180 Lat -90 to 90, altitude in meters",
"GPS":
{
"Latitude": 31.6543,
"Longitude": -91.6543,
"Altitude": 640.1,
"__comment_gps__": "Time is in UTC gps synchronized for this block",
"Time": "2023-01-18T14:29:18.740561"
},
"Split": true,
"SatMode": false,
"__comment_Time__": "Time is from internal rig not GPS synced so may be inaccurate -- see GPS Time",
"Time": "2023-01-18T14:29:18.740561",
"Rig": "Dummy",
"App": "Hamlib",
"__comment_version__": "protocol version date YYYYMMDD",
"Version": "20210520",
"__comment_seq__": "Seq is 1-up sequence number 32-bit -- wraps around to 1 from 2^32-1",
"Seq": 1,
"__comment_crc__": "32-bit CRC of entire JSON record replacing the CRC value with 0x00000000",
"CRC": "0x00000000"
{
,
"Name": "VFOA",
"Freq": 14086800,
"Mode": "AM",
"Width": 9000,
"RX": True,
"TX": False
}

Wyświetl plik

@ -2464,6 +2464,21 @@ struct rig_cache {
int satmode; // if rig is in satellite mode
};
/**
* \brief Multicast data items the are unique per rig instantiation
* This is meant for internal Hamlib use only
*/
#include <multicast.h>
struct multicast_s
{
int multicast_running;
int sock;
int seqnumber;
int runflag; // = 0;
struct ip_mreq mreq; // = {0};
pthread_t threadid;
struct sockaddr_in dest_addr; // = {0};
};
/**
* \brief Rig state containing live data and customized fields.
@ -2594,6 +2609,7 @@ struct rig_state {
char client_version[32]; /*!<! Allow client to report version for compatibility checks/capability */
freq_t offset_vfoa; /*!< Offset to apply to VFOA/Main set_freq */
freq_t offset_vfob; /*!< Offset to apply to VFOB/Sub set_freq */
struct multicast_s *multicast; /*!< Pointer to multicast server data */
};
//! @cond Doxygen_Suppress

396
src/multicast.c 100644
Wyświetl plik

@ -0,0 +1,396 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
//#include <netinet/in.h>
#include <arpa/inet.h>
#include "hamlib/rig.h"
#include "misc.h"
#include "multicast.h"
#define RIG_MULTICAST_ADDR "224.0.0.1"
#define RIG_MULTICAST_PORT 4532
#if 0
static int multicast_running;
static int sock;
static int seqnumber;
static int runflag = 0;
static struct ip_mreq mreq = {0};
static pthread_t threadid;
static struct sockaddr_in dest_addr = {0};
#endif
static int multicast_status_changed(RIG *rig)
{
int retval;
#if 0
freq_t freq, freqsave = rig->state.cache.freqMainA;
if ((retval = rig_get_freq(rig, RIG_VFO_A, &freq)) != RIG_OK)
{
rig_debug(RIG_DEBUG_ERR, "%s: rig_get_freq:%s\n", __func__, rigerror(retval));
}
if (freq != freqsave) { return 1; }
#endif
rmode_t mode, modesave = rig->state.cache.modeMainA;
pbwidth_t width, widthsave = rig->state.cache.widthMainA;
if (rig->state.multicast->seqnumber % 2 == 0
&& (retval = rig_get_mode(rig, RIG_VFO_A, &mode, &width)) != RIG_OK)
{
rig_debug(RIG_DEBUG_ERR, "%s: rig_get_freq:%s\n", __func__, rigerror(retval));
}
if (mode != modesave) { return 1; }
if (width != widthsave) { return 1; }
ptt_t ptt, pttsave = rig->state.cache.ptt;
if (rig->state.multicast->seqnumber % 2 == 0
&& (retval = rig_get_ptt(rig, RIG_VFO_A, &ptt)) != RIG_OK)
if (ptt != pttsave) { return 1; }
split_t split, splitsave = rig->state.cache.split;
vfo_t txvfo;
if (rig->state.multicast->seqnumber % 2 == 0
&& (retval = rig_get_split_vfo(rig, RIG_VFO_A, &split, &txvfo)) != RIG_OK)
if (split != splitsave) { return 1; }
return 0;
}
void json_add_string(char *msg, const char *key, const char *value)
{
if (strlen(msg) != 0)
{
strcat(msg, ",\n");
}
strcat(msg, "\"");
strcat(msg, key);
strcat(msg, "\": ");
strcat(msg, "\"");
strcat(msg, value);
strcat(msg, "\"");
}
void json_add_int(char *msg, const char *key, const int number)
{
if (strlen(msg) != 0)
{
strcat(msg, ",\n");
}
strcat(msg, "\"");
strcat(msg, key);
strcat(msg, "\": ");
char tmp[64];
sprintf(tmp, "%d", number);
strcat(msg, tmp);
}
void json_add_double(char *msg, const char *key, const double value)
{
if (strlen(msg) != 0)
{
strcat(msg, ",\n");
}
strcat(msg, "\"");
strcat(msg, key);
strcat(msg, "\": ");
char tmp[64];
sprintf(tmp, "%f", value);
strcat(msg, tmp);
}
void json_add_boolean(char *msg, const char *key, const int value)
{
if (strlen(msg) != 0)
{
strcat(msg, ",\n");
}
strcat(msg, "\"");
strcat(msg, key);
strcat(msg, "\": ");
char tmp[64];
sprintf(tmp, "%s", value == 0 ? "False" : "True");
strcat(msg, tmp);
}
void json_add_time(char *msg)
{
char mydate[256];
date_strget(mydate, sizeof(mydate), 0);
if (strlen(msg) != 0)
{
strcat(msg, ",\n");
}
strcat(msg, "\"Time\": ");
strcat(msg, mydate);
}
void json_add_vfoA(RIG *rig, char *msg)
{
strcat(msg, "{\n");
json_add_string(msg, "Name", "VFOA");
json_add_int(msg, "Freq", rig->state.cache.freqMainA);
if (strlen(rig_strrmode(rig->state.cache.modeMainA)) > 0)
{
json_add_string(msg, "Mode", rig_strrmode(rig->state.cache.modeMainA));
}
if (rig->state.cache.widthMainA > 0)
{
json_add_int(msg, "Width", rig->state.cache.widthMainA);
}
json_add_boolean(msg, "RX", !rig->state.cache.ptt);
json_add_boolean(msg, "TX", rig->state.cache.ptt);
strcat(msg, "\n}\n");
}
void json_add_vfoB(RIG *rig, char *msg)
{
strcat(msg, "{\n");
json_add_string(msg, "Name", "VFOB");
json_add_int(msg, "Freq", rig->state.cache.freqMainB);
if (strlen(rig_strrmode(rig->state.cache.modeMainB)) > 0)
{
json_add_string(msg, "Mode", rig_strrmode(rig->state.cache.modeMainB));
}
if (rig->state.cache.widthMainB > 0)
{
json_add_int(msg, "Width", rig->state.cache.widthMainB);
}
json_add_boolean(msg, "RX", !rig->state.cache.ptt);
json_add_boolean(msg, "TX", rig->state.cache.ptt);
strcat(msg, "\n},\n");
}
static int multicast_send_json(RIG *rig)
{
char msg[8192]; // could be pretty big
char buf[4096];
// sprintf(msg,"%s:f=%.1f", date_strget(msg, (int)sizeof(msg), 0), f);
msg[0] = 0;
snprintf(buf, sizeof(buf), "%s:%s", rig->caps->model_name,
rig->state.rigport.pathname);
json_add_string(msg, "ID", buf);
json_add_time(msg);
json_add_int(msg, "Sequence", rig->state.multicast->seqnumber++);
json_add_string(msg, "VFOCurr", rig_strvfo(rig->state.current_vfo));
json_add_int(msg, "Split", rig->state.cache.split);
strcat(msg, ",\n\"VFOs\": [\n{\n");
json_add_vfoA(rig, msg);
// send the thing
multicast_send(rig, (unsigned char *)msg, strlen(msg));
return 0;
}
void *multicast_thread(void *vrig)
{
int retval;
RIG *rig = (RIG *)vrig;
rig_debug(RIG_DEBUG_TRACE, "%s: multicast_thread started\n", __func__);
// do the 1st packet all the time
multicast_status_changed(rig);
multicast_send_json(rig);
int loopcount = 4;
while (rig->state.multicast->runflag)
{
hl_usleep(100 * 1000);
freq_t freq, freqsave = 0;
if ((retval = rig_get_freq(rig, RIG_VFO_A, &freq)) != RIG_OK)
{
rig_debug(RIG_DEBUG_ERR, "%s: rig_get_freq:%s\n", __func__, rigerror(retval));
}
if (freq != freqsave || loopcount-- == 0)
{
multicast_status_changed(rig);
multicast_send_json(rig);
loopcount = 4;
}
}
return NULL;
}
int multicast_init(RIG *rig, char *addr, int port)
{
if (rig->state.multicast == NULL)
{
rig->state.multicast = calloc(1, sizeof(struct multicast_s));
}
else if (rig->state.multicast->multicast_running) { return RIG_OK; } // we only need one port
if (addr == NULL) { addr = RIG_MULTICAST_ADDR; }
if (port == 0) { port = RIG_MULTICAST_PORT; }
// Create a UDP socket
rig->state.multicast->sock = socket(AF_INET, SOCK_DGRAM, 0);
if (rig->state.multicast->sock < 0)
{
rig_debug(RIG_DEBUG_ERR, "%s: socket: %s\n", __func__, strerror(errno));
return -RIG_EIO;
}
// Set the SO_REUSEADDR option to allow multiple processes to use the same address
int optval = 1;
if (setsockopt(rig->state.multicast->sock, SOL_SOCKET, SO_REUSEADDR, &optval,
sizeof(optval)) < 0)
{
rig_debug(RIG_DEBUG_ERR, "%s: setsockopt: %s\n", __func__, strerror(errno));
return -RIG_EIO;
}
// Bind the socket to any available local address and the specified port
struct sockaddr_in saddr = {0};
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
saddr.sin_port = htons(port);
if (bind(rig->state.multicast->sock, (struct sockaddr *)&saddr,
sizeof(saddr)) < 0)
{
rig_debug(RIG_DEBUG_ERR, "%s: bind: %s\n", __func__, strerror(errno));
return -RIG_EIO;
}
// Construct the multicast group address
// struct ip_mreq mreq = {0};
rig->state.multicast->mreq.imr_multiaddr.s_addr = inet_addr(addr);
rig->state.multicast->mreq.imr_interface.s_addr = htonl(INADDR_ANY);
// Set the multicast TTL (time-to-live) to limit the scope of the packets
unsigned char ttl = 1;
if (setsockopt(rig->state.multicast->sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
sizeof(ttl)) < 0)
{
rig_debug(RIG_DEBUG_ERR, "%s: setsockopt: %s\n", __func__, strerror(errno));
return -RIG_EIO;
}
// Join the multicast group
if (setsockopt(rig->state.multicast->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&rig->state.multicast->mreq, sizeof(rig->state.multicast->mreq)) < 0)
{
rig_debug(RIG_DEBUG_ERR, "%s: setsockopt: %s\n", __func__, strerror(errno));
return -RIG_EIO;
}
// prime the dest_addr for the send routine
rig->state.multicast->dest_addr.sin_family = AF_INET;
rig->state.multicast->dest_addr.sin_addr.s_addr = inet_addr(addr);
rig->state.multicast->dest_addr.sin_port = htons(port);
printf("starting thread\n");
rig->state.multicast->runflag = 1;
pthread_create(&rig->state.multicast->threadid, NULL, multicast_thread,
(void *)rig);
rig->state.multicast->multicast_running = 1;
return RIG_OK;
}
void multicast_close(RIG *rig)
{
int retval;
// Leave the multicast group
if ((retval = setsockopt(rig->state.multicast->sock, IPPROTO_IP,
IP_DROP_MEMBERSHIP, &rig->state.multicast->mreq,
sizeof(rig->state.multicast->mreq))) < 0)
{
rig_debug(RIG_DEBUG_ERR, "%s: setsockopt: %s\n", __func__, strerror(errno));
return;
}
// Close the socket
if ((retval = close(rig->state.multicast->sock)))
{
rig_debug(RIG_DEBUG_ERR, "%s: close: %s\n", __func__, strerror(errno));
}
}
// if msglen=0 msg is assumed to be a string
int multicast_send(RIG *rig, unsigned char *msg, int msglen)
{
// Construct the message to send
if (msglen == 0) { msglen = strlen((char *)msg); }
// Send the message to the multicast group
ssize_t num_bytes = sendto(rig->state.multicast->sock, msg, msglen, 0,
(struct sockaddr *)&rig->state.multicast->dest_addr,
sizeof(rig->state.multicast->dest_addr));
if (num_bytes < 0)
{
rig_debug(RIG_DEBUG_ERR, "%s: sendto: %s\n", __func__, strerror(errno));
return -RIG_EIO;
}
else
{
// printf("Sent %zd bytes to multicast group %s:%d\n", num_bytes, MULTICAST_ADDR,
// PORT);
}
return num_bytes;
}
#define TEST
#ifdef TEST
int main(int argc, char *argv[])
{
RIG *rig;
rig_model_t myrig_model;
rig_set_debug_level(RIG_DEBUG_NONE);
if (argc > 1) { myrig_model = atoi(argv[1]); }
else
{
myrig_model = 1035;
}
rig = rig_init(myrig_model);
if (rig == NULL)
{
}
strncpy(rig->state.rigport.pathname, "/dev/ttyUSB0", HAMLIB_FILPATHLEN - 1);
rig->state.rigport.parm.serial.rate = 38400;
rig_open(rig);
multicast_init(rig, NULL, 0);
pthread_join(rig->state.multicast->threadid, NULL);
pthread_exit(NULL);
return 0;
}
#endif

47
src/multicast.h 100644
Wyświetl plik

@ -0,0 +1,47 @@
//include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
//#include <errno.h>
//#include <unistd.h>
#include <hamlib/rig.h>
//#include <sys/socket.h>
//#include <netinet/in.h>
//#include <arpa/inet.h>
#ifndef MULTICAST_H
#define MULTICAST_H
#if 0 // moved to rig.h
struct multicast_s
{
int multicast_running;
int sock;
int seqnumber;
int runflag; // = 0;
struct ip_mreq mreq; // = {0};
pthread_t threadid;
struct sockaddr_in dest_addr; // = {0};
};
#endif
struct multicast_vfo
{
char *name;
double freq;
char *mode;
int width;
int widthLower;
int widthUpper;
unsigned char rx; // true if in rx mode
unsigned char tx; // true in in tx mode
};
struct multicast_broadcast
{
char *ID;
struct multicast_vfo **vfo;
};
// returns # of bytes sent
int multicast_init(RIG *rig, char *addr, int port);
int multicast_send(RIG *rig, unsigned char *msg, int msglen);
#endif //MULTICAST_H

Wyświetl plik

@ -0,0 +1,88 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MULTICAST_ADDR "224.0.0.1"
#define PORT 4532
#define BUFFER_SIZE 1024
int main()
{
// Create a UDP socket
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Set the SO_REUSEADDR option to allow multiple processes to use the same address
int optval = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
{
perror("setsockopt failed");
exit(EXIT_FAILURE);
}
// Bind the socket to any available local address and the specified port
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PORT);
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind failed");
exit(EXIT_FAILURE);
}
// Construct the multicast group address
struct ip_mreq mreq = {0};
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_ADDR);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
// Join the multicast group
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
perror("setsockopt failed");
exit(EXIT_FAILURE);
}
// Receive multicast packets in a loop
while (1)
{
char buffer[BUFFER_SIZE];
struct sockaddr_in src_addr = {0};
socklen_t addrlen = sizeof(src_addr);
ssize_t num_bytes = recvfrom(sock, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr *)&src_addr, &addrlen);
if (num_bytes < 0)
{
perror("recvfrom failed");
exit(EXIT_FAILURE);
}
buffer[num_bytes] = '\0';
printf("Received %zd bytes from %s:%d\n%s\n", num_bytes,
inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port), buffer);
}
// Leave the multicast group
if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
perror("setsockopt failed");
exit(EXIT_FAILURE);
}
// Close the socket
close(sock);
return 0;
}