Quisk version 4.2.51
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
Quisk Version 4.2.51 May 2026
|
||||
==============================
|
||||
This version can use the TCI protocol to connect to WSJT-X. Please see Digital Modes in the documentation.
|
||||
|
||||
Quisk Version 4.2.50 December 2025
|
||||
===================================
|
||||
This is a quick fix for a bug in 4.2.49 that results in the repeated message
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.50
|
||||
#Quisk version 4.2.51
|
||||
from .quisk import main
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Base64 encoding/decoding (RFC1341)
|
||||
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "base64.h"
|
||||
|
||||
static const unsigned char base64_table[65] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
/**
|
||||
* base64_encode - Base64 encode
|
||||
* @src: Data to be encoded
|
||||
* @len: Length of the data to be encoded
|
||||
* @out_len: Pointer to output length variable, or %NULL if not used
|
||||
* Returns: Allocated buffer of out_len bytes of encoded data,
|
||||
* or %NULL on failure
|
||||
*
|
||||
* Caller is responsible for freeing the returned buffer. Returned buffer is
|
||||
* nul terminated to make it easier to use as a C string. The nul terminator is
|
||||
* not included in out_len.
|
||||
*/
|
||||
unsigned char * base64_encode(const unsigned char *src, size_t len,
|
||||
size_t *out_len)
|
||||
{
|
||||
unsigned char *out, *pos;
|
||||
const unsigned char *end, *in;
|
||||
size_t olen;
|
||||
int line_len;
|
||||
|
||||
olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
|
||||
olen += olen / 72; /* line feeds */
|
||||
olen++; /* nul termination */
|
||||
if (olen < len)
|
||||
return NULL; /* integer overflow */
|
||||
out = malloc(olen);
|
||||
if (out == NULL)
|
||||
return NULL;
|
||||
|
||||
end = src + len;
|
||||
in = src;
|
||||
pos = out;
|
||||
line_len = 0;
|
||||
while (end - in >= 3) {
|
||||
*pos++ = base64_table[in[0] >> 2];
|
||||
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
|
||||
*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
|
||||
*pos++ = base64_table[in[2] & 0x3f];
|
||||
in += 3;
|
||||
line_len += 4;
|
||||
if (line_len >= 72) {
|
||||
*pos++ = '\n';
|
||||
line_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (end - in) {
|
||||
*pos++ = base64_table[in[0] >> 2];
|
||||
if (end - in == 1) {
|
||||
*pos++ = base64_table[(in[0] & 0x03) << 4];
|
||||
*pos++ = '=';
|
||||
} else {
|
||||
*pos++ = base64_table[((in[0] & 0x03) << 4) |
|
||||
(in[1] >> 4)];
|
||||
*pos++ = base64_table[(in[1] & 0x0f) << 2];
|
||||
}
|
||||
*pos++ = '=';
|
||||
line_len += 4;
|
||||
}
|
||||
|
||||
if (line_len)
|
||||
*pos++ = '\n';
|
||||
|
||||
*pos = '\0';
|
||||
if (out_len)
|
||||
*out_len = pos - out;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* base64_decode - Base64 decode
|
||||
* @src: Data to be decoded
|
||||
* @len: Length of the data to be decoded
|
||||
* @out_len: Pointer to output length variable
|
||||
* Returns: Allocated buffer of out_len bytes of decoded data,
|
||||
* or %NULL on failure
|
||||
*
|
||||
* Caller is responsible for freeing the returned buffer.
|
||||
*/
|
||||
unsigned char * base64_decode(const unsigned char *src, size_t len,
|
||||
size_t *out_len)
|
||||
{
|
||||
unsigned char dtable[256], *out, *pos, block[4], tmp;
|
||||
size_t i, count, olen;
|
||||
int pad = 0;
|
||||
|
||||
memset(dtable, 0x80, 256);
|
||||
for (i = 0; i < sizeof(base64_table) - 1; i++)
|
||||
dtable[base64_table[i]] = (unsigned char) i;
|
||||
dtable['='] = 0;
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (dtable[src[i]] != 0x80)
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0 || count % 4)
|
||||
return NULL;
|
||||
|
||||
olen = count / 4 * 3;
|
||||
pos = out = malloc(olen);
|
||||
if (out == NULL)
|
||||
return NULL;
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < len; i++) {
|
||||
tmp = dtable[src[i]];
|
||||
if (tmp == 0x80)
|
||||
continue;
|
||||
|
||||
if (src[i] == '=')
|
||||
pad++;
|
||||
block[count] = tmp;
|
||||
count++;
|
||||
if (count == 4) {
|
||||
*pos++ = (block[0] << 2) | (block[1] >> 4);
|
||||
*pos++ = (block[1] << 4) | (block[2] >> 2);
|
||||
*pos++ = (block[2] << 6) | block[3];
|
||||
count = 0;
|
||||
if (pad) {
|
||||
if (pad == 1)
|
||||
pos--;
|
||||
else if (pad == 2)
|
||||
pos -= 2;
|
||||
else {
|
||||
/* Invalid padding */
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*out_len = pos - out;
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Base64 encoding/decoding (RFC1341)
|
||||
* Copyright (c) 2005, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*/
|
||||
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
unsigned char * base64_encode(const unsigned char *src, size_t len,
|
||||
size_t *out_len);
|
||||
unsigned char * base64_decode(const unsigned char *src, size_t len,
|
||||
size_t *out_len);
|
||||
|
||||
#endif /* BASE64_H */
|
||||
@@ -4203,6 +4203,9 @@ class RadioRemote(BaseWindow): # The Remote page in the second-level notebook f
|
||||
heading = None
|
||||
self.NextRow()
|
||||
self.col = 2
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("TCI", text,
|
||||
'Rig control commands and digital sample streams are sent to multiple clients using the TCI protocol.')
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("Hamlib Rig 2", text,
|
||||
'Rig control commands using "Hamlib Rig 2" are sent to a Quisk TCP port from multiple clients.')
|
||||
|
||||
@@ -1434,9 +1434,35 @@ is no longer needed
|
||||
<br>
|
||||
</p>
|
||||
<h2 id="Digital">Digital Modes </h2>
|
||||
<br>
|
||||
<h3>Digital Modes with the TCI Protocol</h3>
|
||||
<p>
|
||||
Using the sound devices in a PC to transfer digital sample streams has always caused problems.
|
||||
Several new methods are now becoming available. These are the <a href="https://github.com/ExpertSDR3/TCI">TCI protocol</a>
|
||||
used by <a href="https://eesdr.com/en/">Expert Electronics</a> for
|
||||
their SunSDR hardware
|
||||
and <a href="https://www.vita.com/page-1855484">Vita49</a> used by <a href="https://www.flexradio.com">FlexRadio</a>.
|
||||
And Hamlib is planning to add digital stream support.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
Quisk can now connect to WSJT-X version 3.0.0 with the TCI protocol.
|
||||
On the Quisk Config/radio/Remote screen enter IP address 127.0.0.1 and port 40001.
|
||||
Use USB for digital modes. The DGT-* modes are for sound cards.
|
||||
In WSJT-X choose the radio "TCI Client RX1", and for audio choose "TCI audio".
|
||||
And enter the same IP and port. Everything then just works.
|
||||
If your WSJT-X does not have "TCI Client RX1" you need to update to a more recent version.
|
||||
This feature is new. To see the TCI conversation, use the "Debug level" on the Config/radio/Options screen.
|
||||
</p>
|
||||
<br>
|
||||
<h3>Digital Modes with Sound Cards</h3>
|
||||
<p>
|
||||
Digital modes need to receive the audio from the radio to decode it.
|
||||
They need to send audio to the radio to transmit.
|
||||
These audio streams have traditionally been sent using PC sound cards or
|
||||
an artificial sound card known as a Virtual Audio Cable.
|
||||
Quisk has a number of modes "DGT-" to receive and transmit digital
|
||||
signals. The modes "DGT-U" and "DGT-L" decode the signal as upper
|
||||
signals on sound devices. The modes "DGT-U" and "DGT-L" decode the signal as upper
|
||||
or lower sideband, and send the stereo audio to the digital sound
|
||||
device. The left and right channel are the same.
|
||||
The bandwidth is set with the filter buttons as
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2024 Davidson Francis <davidsondfgl@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "base64.h"
|
||||
#include "sha1.h"
|
||||
#include "ws.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
/**
|
||||
* @dir src/
|
||||
* @brief Handshake routines directory
|
||||
*
|
||||
* @file handshake.c
|
||||
* @brief Handshake routines.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Gets the field Sec-WebSocket-Accept on response, by
|
||||
* an previously informed key.
|
||||
*
|
||||
* @param wsKey Sec-WebSocket-Key
|
||||
* @param dest source to be stored the value.
|
||||
*
|
||||
* @return Returns 0 if success and a negative number
|
||||
* otherwise.
|
||||
*
|
||||
* @attention This is part of the internal API and is documented just
|
||||
* for completeness.
|
||||
*/
|
||||
int get_handshake_accept(char *wsKey, unsigned char **dest)
|
||||
{
|
||||
unsigned char hash[SHA1HashSize]; /* SHA-1 Hash. */
|
||||
SHA1Context ctx; /* SHA-1 Context. */
|
||||
char *str; /* WebSocket key + magic string. */
|
||||
|
||||
/* Invalid key. */
|
||||
if (!wsKey)
|
||||
return (-1);
|
||||
|
||||
str = calloc(1, sizeof(char) * (WS_KEY_LEN + WS_MS_LEN + 1));
|
||||
if (!str)
|
||||
return (-1);
|
||||
|
||||
strncpy(str, wsKey, WS_KEY_LEN);
|
||||
strcat(str, MAGIC_STRING);
|
||||
|
||||
SHA1Reset(&ctx);
|
||||
SHA1Input(&ctx, (const uint8_t *)str, WS_KEYMS_LEN);
|
||||
SHA1Result(&ctx, hash);
|
||||
|
||||
*dest = base64_encode(hash, SHA1HashSize, NULL);
|
||||
*(*dest + strlen((const char *)*dest) - 1) = '\0';
|
||||
free(str);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds the ocorrence of @p needle in @p haystack, case
|
||||
* insensitive.
|
||||
*
|
||||
* @param haystack Target string to be searched.
|
||||
* @param needle Substring to search for.
|
||||
*
|
||||
* @returns If found, returns a pointer at the beginning of the
|
||||
* found substring. Otherwise, returns NULL.
|
||||
*/
|
||||
static const char *strstricase(const char *haystack, const char *needle)
|
||||
{
|
||||
size_t length;
|
||||
for (length = strlen(needle); *haystack; haystack++)
|
||||
if (!strncasecmp(haystack, needle, length))
|
||||
return haystack;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the complete response to accomplish a succesfully
|
||||
* handshake.
|
||||
*
|
||||
* @param hsrequest Client request.
|
||||
* @param hsresponse Server response.
|
||||
*
|
||||
* @return Returns 0 if success and a negative number
|
||||
* otherwise.
|
||||
*
|
||||
* @attention This is part of the internal API and is documented just
|
||||
* for completeness.
|
||||
*/
|
||||
int get_handshake_response(char *hsrequest, char **hsresponse)
|
||||
{
|
||||
unsigned char *accept; /* Accept message. */
|
||||
char *saveptr; /* strtok_r() pointer. */
|
||||
char *s; /* Current string. */
|
||||
int ret; /* Return value. */
|
||||
|
||||
saveptr = NULL;
|
||||
for (s = strtok_r(hsrequest, "\r\n", &saveptr); s != NULL;
|
||||
s = strtok_r(NULL, "\r\n", &saveptr))
|
||||
{
|
||||
if (strstricase(s, WS_HS_REQ) != NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Ensure that we have a valid pointer. */
|
||||
if (s == NULL)
|
||||
return (-1);
|
||||
|
||||
saveptr = NULL;
|
||||
s = strtok_r(s, " ", &saveptr);
|
||||
s = strtok_r(NULL, " ", &saveptr);
|
||||
|
||||
ret = get_handshake_accept(s, &accept);
|
||||
if (ret < 0)
|
||||
return (ret);
|
||||
|
||||
*hsresponse = malloc(sizeof(char) * WS_HS_ACCLEN);
|
||||
if (*hsresponse == NULL)
|
||||
return (-1);
|
||||
|
||||
strcpy(*hsresponse, WS_HS_ACCEPT);
|
||||
strcat(*hsresponse, (const char *)accept);
|
||||
strcat(*hsresponse, "\r\n\r\n");
|
||||
|
||||
free(accept);
|
||||
return (0);
|
||||
}
|
||||
@@ -1219,6 +1219,10 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
|
||||
count *= interp;
|
||||
transmit_mic_carrier(cSamples, count, quiskSpotLevel / 1000.0);
|
||||
}
|
||||
else if (tci_tx_audio_client) {
|
||||
count = tx_filter_digital(cSamples, count); // filter samples, minimal processing
|
||||
process_alc(cSamples, count, &tx_alc, DGT_U);
|
||||
}
|
||||
else switch (rxMode) {
|
||||
case LSB: // LSB
|
||||
case USB: // USB
|
||||
|
||||
@@ -6188,6 +6188,8 @@ static PyMethodDef QuiskMethods[] = {
|
||||
{"watfall_GetPixels", watfall_GetPixels, METH_VARARGS, "Write the Waterfall image to be displayed."},
|
||||
{"write_fftw_wisdom", write_fftw_wisdom, METH_VARARGS, "Write the current fftw wisdom to the wisdom file."},
|
||||
{"read_fftw_wisdom", read_fftw_wisdom, METH_VARARGS, "Return the current fftw wisdom as a byte array."},
|
||||
{"tci_get_params", (PyCFunction)quisk_tci_get_params, METH_VARARGS, "Return parameters from TCI."},
|
||||
{"tci_set_params", (PyCFunction)quisk_tci_set_params, METH_VARARGS|METH_KEYWORDS, "Set parameters for TCI."},
|
||||
// Remote Quisk control head and slave by Ben, AC2YD
|
||||
{"start_control_head_remote_sound", quisk_start_control_head_remote_sound, METH_VARARGS, "Start running UDP remote sound on control_head."},
|
||||
{"stop_control_head_remote_sound", quisk_stop_control_head_remote_sound, METH_VARARGS, "Stop running UDP remote sound on control_head."},
|
||||
|
||||
@@ -260,6 +260,13 @@ extern PyObject * quisk_stop_remote_radio_remote_sound(PyObject * self, PyObject
|
||||
extern int receive_graph_data(double * fft_avg);
|
||||
extern void send_graph_data(double * fft_avg, int fft_size, double zoom, double deltaf, int fft_sample_rate, double scale);
|
||||
|
||||
// TCI server
|
||||
PyObject * quisk_tci_set_params(PyObject * self, PyObject * args, PyObject * keywds);
|
||||
PyObject * quisk_tci_get_params(PyObject * self, PyObject * args);
|
||||
void tci_send_audio(complex double * cSamples, int nSamples);
|
||||
int tci_get_mic(complex double * cSamples, int mic_count);
|
||||
extern uint64_t tci_tx_audio_client; // This is ws_cli_conn_t defined in ws.h.
|
||||
|
||||
void QuiskWavClose(struct QuiskWav *);
|
||||
int QuiskWavWriteOpen(struct QuiskWav *, char *, short, short, short, int, double);
|
||||
void QuiskWavWriteC(struct QuiskWav *, complex double *, int);
|
||||
@@ -394,6 +401,7 @@ int quisk_play_sidetone(struct sound_dev *);
|
||||
void quisk_set_play_state(void);
|
||||
void quisk_poll_hardware_key(void);
|
||||
void PreDistort(complex double * amp_in_samples, complex double * amp_out_samples, int nSamples, complex double * tx_samples, int num_tx);
|
||||
int CircularBuffer(int channel, complex double * cSamples, int nRead, int nWrite);
|
||||
|
||||
// Driver function definitions=================================================
|
||||
int quisk_read_alsa(struct sound_dev *, complex double *);
|
||||
|
||||
@@ -3938,6 +3938,7 @@ class App(wx.App):
|
||||
self.hot_key_ptt_active = False
|
||||
self.serial_ptt_active = False
|
||||
self.vox_ptt_active = False
|
||||
self.tci_ptt_active = False
|
||||
self.hermes_LNA_dB = 20
|
||||
self.hermes_atten_dB = 0
|
||||
if hasattr(Hardware, "OnChangeRxTx"):
|
||||
@@ -4028,6 +4029,8 @@ class App(wx.App):
|
||||
self.file_name_play_samples = ''
|
||||
self.file_name_play_cq = ''
|
||||
self.midiControls = {} # Control object and associated function for Midi control name
|
||||
self.tci_vfo = 0
|
||||
self.tci_started = False
|
||||
# get the screen size - thanks to Lucian Langa
|
||||
x, y, self.screen_width, self.screen_height = wx.Display().GetGeometry() # Using display index 0
|
||||
self.Bind(wx.EVT_IDLE, self.OnIdle)
|
||||
@@ -4353,6 +4356,19 @@ class App(wx.App):
|
||||
if hasattr(bw, "sliderLNA"):
|
||||
self.midiControls["RfLna"] = (bw.sliderLNA, bw.OnLNA)
|
||||
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyHook)
|
||||
# Start the TCI server
|
||||
if conf.tci_ip and conf.tci_port:
|
||||
value = getattr(conf, "pulse_audio_verbose_output")
|
||||
QS.tci_set_params(verbose=value)
|
||||
QS.tci_set_params(start=1);
|
||||
self.tci_started = True
|
||||
QS.tci_set_params(tci_dds=self.VFO)
|
||||
QS.tci_set_params(tci_if=self.txFreq)
|
||||
self.tci_vfo = self.txFreq + self.VFO
|
||||
QS.tci_set_params(tci_vfo=self.tci_vfo)
|
||||
QS.tci_set_params(tci_modulation=self.mode)
|
||||
QS.tci_set_params(tci_trx=0)
|
||||
QS.tci_set_params(tci_split_enable=0)
|
||||
return True
|
||||
def OnStartWsjtx(self, ctrl):
|
||||
method = ctrl.GetValue()
|
||||
@@ -4762,6 +4778,7 @@ class App(wx.App):
|
||||
def ImmediateChange(self, name):
|
||||
if name == "pulse_audio_verbose_output":
|
||||
value = getattr(conf, name)
|
||||
QS.tci_set_params(verbose=value)
|
||||
if value == 0:
|
||||
self.std_out_err.Show(False)
|
||||
else:
|
||||
@@ -5765,6 +5782,7 @@ class App(wx.App):
|
||||
QS.set_sidetone(self.sidetone_volume, self.sidetone_0to1, self.ritFreq, conf.keyupDelay)
|
||||
def OnBtnSplit(self, event): # Called when the Split check button is pressed
|
||||
self.split_rxtx = self.splitButton.GetValue()
|
||||
QS.tci_set_params(tci_split_enable=self.split_rxtx)
|
||||
if self.split_rxtx:
|
||||
if self.split_offset == 0:
|
||||
if self.mode in ("CWL", "CWU"):
|
||||
@@ -6150,6 +6168,7 @@ class App(wx.App):
|
||||
self.ChangeRxTxFrequency(self.rxFreq + delta + self.VFO, self.txFreq + delta + self.VFO)
|
||||
Hardware.ChangeMode(mode)
|
||||
self.mode = mode
|
||||
QS.tci_set_params(tci_modulation=mode)
|
||||
self.MakeFilterButtons(self.Mode2Filters(mode))
|
||||
QS.set_rx_mode(Mode2Index.get(mode, 3))
|
||||
if mode == 'CWL':
|
||||
@@ -6677,11 +6696,13 @@ class App(wx.App):
|
||||
if not self.tx_indicator:
|
||||
self.tx_indicator = True
|
||||
self.pttButton.Tx.TurnOn(True)
|
||||
QS.tci_set_params(tci_trx=1)
|
||||
else:
|
||||
if self.tx_indicator:
|
||||
self.tx_indicator = False
|
||||
self.pttButton.Tx.TurnOn(False)
|
||||
if True: # Manage the PTT button using serial port, VOX, hot keys, WAV file play, PTT button, MIDI, CAT
|
||||
QS.tci_set_params(tci_trx=0)
|
||||
if True: # Manage the PTT button using serial port, VOX, hot keys, WAV file play, PTT button, MIDI, CAT, TCI
|
||||
ptt_button_down = self.pttButton.GetValue()
|
||||
ptt = None
|
||||
if conf.quisk_serial_cts[0:4] == "PTT " or conf.quisk_serial_dsr[0:4] == "PTT ":
|
||||
@@ -6736,6 +6757,14 @@ class App(wx.App):
|
||||
self.hot_key_ptt_pressed = False
|
||||
if self.tx_inhibit:
|
||||
ptt = False
|
||||
tci = QS.tci_get_params("tci_trx")
|
||||
if tci is not None:
|
||||
if tci:
|
||||
self.tci_ptt_active = True
|
||||
ptt = True
|
||||
elif self.tci_ptt_active:
|
||||
self.tci_ptt_active = False
|
||||
ptt = False
|
||||
if ptt is True and not ptt_button_down:
|
||||
self.SetPTT(True)
|
||||
elif ptt is False and ptt_button_down:
|
||||
@@ -6803,6 +6832,19 @@ class App(wx.App):
|
||||
if self.timer - self.heart_time0 > 0.10: # call hardware to perform background tasks:
|
||||
self.heart_time0 = self.timer
|
||||
Hardware.HeartBeat()
|
||||
if self.tci_started:
|
||||
if self.tci_vfo != self.txFreq + self.VFO: # limit the speed of frequency updates
|
||||
self.tci_vfo = self.txFreq + self.VFO
|
||||
QS.tci_set_params(tci_vfo=self.tci_vfo)
|
||||
freq = QS.tci_get_params("tci_vfo")
|
||||
if freq is not None: # TCI frequency
|
||||
self.ChangeRxTxFrequency(freq, None)
|
||||
mode = QS.tci_get_params("tci_modulation")
|
||||
if mode is not None: # TCI mode
|
||||
self.modeButns.SetLabel(mode, True)
|
||||
split = QS.tci_get_params("tci_split_enable")
|
||||
if split is not None: # TCI split_enable
|
||||
self.splitButton.SetValue(split, True)
|
||||
if self.is_HermesLite2:
|
||||
self.tx_inhibit = QS.get_params('quisk_tx_inhibit')
|
||||
else:
|
||||
|
||||
@@ -1423,6 +1423,19 @@ fixed_tune_offset = 0
|
||||
|
||||
|
||||
################ Remote
|
||||
## tci_ip IP address for TCI, text
|
||||
# Quisk can connect to WSJT-X and other digital programs using the TCI protocol.
|
||||
# Enter the TCI server host name.
|
||||
tci_ip = "127.0.0.1"
|
||||
|
||||
## tci_port IP port for TCI, integer
|
||||
# Quisk can connect to WSJT-X and other digital programs using the TCI protocol.
|
||||
# Enter the TCI port number. If the port number is zero, TCI is turned off.
|
||||
# The preferred port is 40001.
|
||||
tci_port = 0
|
||||
#tci_port = 40001
|
||||
#tci_port = 50001
|
||||
|
||||
## hamlib_ip IP address for Hamlib Rig 2, text
|
||||
# You can control Quisk from Hamlib. Set the Hamlib rig to 2 and the device for rig 2 to
|
||||
# localhost:4575. Or choose a different name and port here. Set the same name and port
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
# You must define the version here. A title string including
|
||||
# the version will be written to __init__.py and read by quisk.py.
|
||||
|
||||
Version = '4.2.50'
|
||||
Version = '4.2.51'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
@@ -16,7 +16,8 @@ fp.close()
|
||||
|
||||
sources = ['quisk.c', 'sound.c', 'is_key_down.c', 'microphone.c', 'utility.c',
|
||||
'sound_alsa.c', 'sound_pulseaudio.c', 'sound_portaudio.c', 'sound_directx.c', 'sound_wasapi.c',
|
||||
'filter.c', 'extdemod.c', 'freedv.c', 'quisk_wdsp.c', 'ac2yd/remote.c']
|
||||
'filter.c', 'extdemod.c', 'freedv.c', 'quisk_wdsp.c', 'ac2yd/remote.c',
|
||||
'tci.c', 'base64.c', 'handshake.c', 'sha1.c', 'utf8.c', 'ws.c']
|
||||
|
||||
# Afedri hardware support added by Alex, Alex@gmail.com
|
||||
mAfedri = Extension ('quisk.afedrinet.afedrinet_io',
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* sha1.c
|
||||
*
|
||||
* Description:
|
||||
* This file implements the Secure Hashing Algorithm 1 as
|
||||
* defined in FIPS PUB 180-1 published April 17, 1995.
|
||||
*
|
||||
* The SHA-1, produces a 160-bit message digest for a given
|
||||
* data stream. It should take about 2**n steps to find a
|
||||
* message with the same digest as a given message and
|
||||
* 2**(n/2) to find any two messages with the same digest,
|
||||
* when n is the digest size in bits. Therefore, this
|
||||
* algorithm can serve as a means of providing a
|
||||
* "fingerprint" for a message.
|
||||
*
|
||||
* Portability Issues:
|
||||
* SHA-1 is defined in terms of 32-bit "words". This code
|
||||
* uses <stdint.h> (included via "sha1.h" to define 32 and 8
|
||||
* bit unsigned integer types. If your C compiler does not
|
||||
* support 32 bit unsigned integers, this code is not
|
||||
* appropriate.
|
||||
*
|
||||
* Caveats:
|
||||
* SHA-1 is designed to work with messages less than 2^64 bits
|
||||
* long. Although SHA-1 allows a message digest to be generated
|
||||
* for messages of any number of bits less than 2^64, this
|
||||
* implementation only works with messages with a length that is
|
||||
* a multiple of the size of an 8-bit character.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sha1.h"
|
||||
|
||||
/*
|
||||
* Define the SHA1 circular left shift macro
|
||||
*/
|
||||
#define SHA1CircularShift(bits,word) \
|
||||
(((word) << (bits)) | ((word) >> (32-(bits))))
|
||||
|
||||
/* Local Function Prototyptes */
|
||||
void SHA1PadMessage(SHA1Context *);
|
||||
void SHA1ProcessMessageBlock(SHA1Context *);
|
||||
|
||||
/*
|
||||
* SHA1Reset
|
||||
*
|
||||
* Description:
|
||||
* This function will initialize the SHA1Context in preparation
|
||||
* for computing a new SHA1 message digest.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The context to reset.
|
||||
*
|
||||
* Returns:
|
||||
* sha Error Code.
|
||||
*
|
||||
*/
|
||||
int SHA1Reset(SHA1Context *context)
|
||||
{
|
||||
if (!context)
|
||||
{
|
||||
return shaNull;
|
||||
}
|
||||
|
||||
context->Length_Low = 0;
|
||||
context->Length_High = 0;
|
||||
context->Message_Block_Index = 0;
|
||||
|
||||
context->Intermediate_Hash[0] = 0x67452301;
|
||||
context->Intermediate_Hash[1] = 0xEFCDAB89;
|
||||
context->Intermediate_Hash[2] = 0x98BADCFE;
|
||||
context->Intermediate_Hash[3] = 0x10325476;
|
||||
context->Intermediate_Hash[4] = 0xC3D2E1F0;
|
||||
|
||||
context->Computed = 0;
|
||||
context->Corrupted = 0;
|
||||
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* SHA1Result
|
||||
*
|
||||
* Description:
|
||||
* This function will return the 160-bit message digest into the
|
||||
* Message_Digest array provided by the caller.
|
||||
* NOTE: The first octet of hash is stored in the 0th element,
|
||||
* the last octet of hash in the 19th element.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The context to use to calculate the SHA-1 hash.
|
||||
* Message_Digest: [out]
|
||||
* Where the digest is returned.
|
||||
*
|
||||
* Returns:
|
||||
* sha Error Code.
|
||||
*
|
||||
*/
|
||||
int SHA1Result( SHA1Context *context,
|
||||
uint8_t Message_Digest[SHA1HashSize])
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!context || !Message_Digest)
|
||||
{
|
||||
return shaNull;
|
||||
}
|
||||
|
||||
if (context->Corrupted)
|
||||
{
|
||||
return context->Corrupted;
|
||||
}
|
||||
|
||||
if (!context->Computed)
|
||||
{
|
||||
SHA1PadMessage(context);
|
||||
for(i=0; i<64; ++i)
|
||||
{
|
||||
/* message may be sensitive, clear it out */
|
||||
context->Message_Block[i] = 0;
|
||||
}
|
||||
context->Length_Low = 0; /* and clear length */
|
||||
context->Length_High = 0;
|
||||
context->Computed = 1;
|
||||
|
||||
}
|
||||
|
||||
for(i = 0; i < SHA1HashSize; ++i)
|
||||
{
|
||||
Message_Digest[i] = context->Intermediate_Hash[i>>2]
|
||||
>> 8 * ( 3 - ( i & 0x03 ) );
|
||||
}
|
||||
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* SHA1Input
|
||||
*
|
||||
* Description:
|
||||
* This function accepts an array of octets as the next portion
|
||||
* of the message.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The SHA context to update
|
||||
* message_array: [in]
|
||||
* An array of characters representing the next portion of
|
||||
* the message.
|
||||
* length: [in]
|
||||
* The length of the message in message_array
|
||||
*
|
||||
* Returns:
|
||||
* sha Error Code.
|
||||
*
|
||||
*/
|
||||
int SHA1Input( SHA1Context *context,
|
||||
const uint8_t *message_array,
|
||||
unsigned length)
|
||||
{
|
||||
if (!length)
|
||||
{
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
if (!context || !message_array)
|
||||
{
|
||||
return shaNull;
|
||||
}
|
||||
|
||||
if (context->Computed)
|
||||
{
|
||||
context->Corrupted = shaStateError;
|
||||
|
||||
return shaStateError;
|
||||
}
|
||||
|
||||
if (context->Corrupted)
|
||||
{
|
||||
return context->Corrupted;
|
||||
}
|
||||
while(length-- && !context->Corrupted)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index++] =
|
||||
(*message_array & 0xFF);
|
||||
|
||||
context->Length_Low += 8;
|
||||
if (context->Length_Low == 0)
|
||||
{
|
||||
context->Length_High++;
|
||||
if (context->Length_High == 0)
|
||||
{
|
||||
/* Message is too long */
|
||||
context->Corrupted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->Message_Block_Index == 64)
|
||||
{
|
||||
SHA1ProcessMessageBlock(context);
|
||||
}
|
||||
|
||||
message_array++;
|
||||
}
|
||||
|
||||
return shaSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* SHA1ProcessMessageBlock
|
||||
*
|
||||
* Description:
|
||||
* This function will process the next 512 bits of the message
|
||||
* stored in the Message_Block array.
|
||||
*
|
||||
* Parameters:
|
||||
* None.
|
||||
*
|
||||
* Returns:
|
||||
* Nothing.
|
||||
*
|
||||
* Comments:
|
||||
|
||||
* Many of the variable names in this code, especially the
|
||||
* single character names, were used because those were the
|
||||
* names used in the publication.
|
||||
*
|
||||
*
|
||||
*/
|
||||
void SHA1ProcessMessageBlock(SHA1Context *context)
|
||||
{
|
||||
const uint32_t K[] = { /* Constants defined in SHA-1 */
|
||||
0x5A827999,
|
||||
0x6ED9EBA1,
|
||||
0x8F1BBCDC,
|
||||
0xCA62C1D6
|
||||
};
|
||||
int t; /* Loop counter */
|
||||
uint32_t temp; /* Temporary word value */
|
||||
uint32_t W[80]; /* Word sequence */
|
||||
uint32_t A, B, C, D, E; /* Word buffers */
|
||||
|
||||
/*
|
||||
* Initialize the first 16 words in the array W
|
||||
*/
|
||||
for(t = 0; t < 16; t++)
|
||||
{
|
||||
W[t] = (uint32_t)context->Message_Block[t * 4] << 24;
|
||||
W[t] |= (uint32_t)context->Message_Block[t * 4 + 1] << 16;
|
||||
W[t] |= (uint32_t)context->Message_Block[t * 4 + 2] << 8;
|
||||
W[t] |= (uint32_t)context->Message_Block[t * 4 + 3];
|
||||
}
|
||||
|
||||
for(t = 16; t < 80; t++)
|
||||
{
|
||||
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
|
||||
}
|
||||
|
||||
A = context->Intermediate_Hash[0];
|
||||
B = context->Intermediate_Hash[1];
|
||||
C = context->Intermediate_Hash[2];
|
||||
D = context->Intermediate_Hash[3];
|
||||
E = context->Intermediate_Hash[4];
|
||||
|
||||
for(t = 0; t < 20; t++)
|
||||
{
|
||||
temp = SHA1CircularShift(5,A) +
|
||||
((B & C) | ((~B) & D)) + E + W[t] + K[0];
|
||||
E = D;
|
||||
D = C;
|
||||
C = SHA1CircularShift(30,B);
|
||||
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for(t = 20; t < 40; t++)
|
||||
{
|
||||
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
|
||||
E = D;
|
||||
D = C;
|
||||
C = SHA1CircularShift(30,B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for(t = 40; t < 60; t++)
|
||||
{
|
||||
temp = SHA1CircularShift(5,A) +
|
||||
((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
|
||||
E = D;
|
||||
D = C;
|
||||
C = SHA1CircularShift(30,B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
for(t = 60; t < 80; t++)
|
||||
{
|
||||
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
|
||||
E = D;
|
||||
D = C;
|
||||
C = SHA1CircularShift(30,B);
|
||||
B = A;
|
||||
A = temp;
|
||||
}
|
||||
|
||||
context->Intermediate_Hash[0] += A;
|
||||
context->Intermediate_Hash[1] += B;
|
||||
context->Intermediate_Hash[2] += C;
|
||||
context->Intermediate_Hash[3] += D;
|
||||
context->Intermediate_Hash[4] += E;
|
||||
|
||||
context->Message_Block_Index = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SHA1PadMessage
|
||||
*
|
||||
|
||||
* Description:
|
||||
* According to the standard, the message must be padded to an even
|
||||
* 512 bits. The first padding bit must be a '1'. The last 64
|
||||
* bits represent the length of the original message. All bits in
|
||||
* between should be 0. This function will pad the message
|
||||
* according to those rules by filling the Message_Block array
|
||||
* accordingly. It will also call the ProcessMessageBlock function
|
||||
* provided appropriately. When it returns, it can be assumed that
|
||||
* the message digest has been computed.
|
||||
*
|
||||
* Parameters:
|
||||
* context: [in/out]
|
||||
* The context to pad
|
||||
* ProcessMessageBlock: [in]
|
||||
* The appropriate SHA*ProcessMessageBlock function
|
||||
* Returns:
|
||||
* Nothing.
|
||||
*
|
||||
*/
|
||||
|
||||
void SHA1PadMessage(SHA1Context *context)
|
||||
{
|
||||
/*
|
||||
* Check to see if the current message block is too small to hold
|
||||
* the initial padding bits and length. If so, we will pad the
|
||||
* block, process it, and then continue padding into a second
|
||||
* block.
|
||||
*/
|
||||
if (context->Message_Block_Index > 55)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index++] = 0x80;
|
||||
while(context->Message_Block_Index < 64)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index++] = 0;
|
||||
}
|
||||
|
||||
SHA1ProcessMessageBlock(context);
|
||||
|
||||
while(context->Message_Block_Index < 56)
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index++] = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context->Message_Block[context->Message_Block_Index++] = 0x80;
|
||||
while(context->Message_Block_Index < 56)
|
||||
{
|
||||
|
||||
context->Message_Block[context->Message_Block_Index++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the message length as the last 8 octets
|
||||
*/
|
||||
context->Message_Block[56] = context->Length_High >> 24;
|
||||
context->Message_Block[57] = context->Length_High >> 16;
|
||||
context->Message_Block[58] = context->Length_High >> 8;
|
||||
context->Message_Block[59] = context->Length_High;
|
||||
context->Message_Block[60] = context->Length_Low >> 24;
|
||||
context->Message_Block[61] = context->Length_Low >> 16;
|
||||
context->Message_Block[62] = context->Length_Low >> 8;
|
||||
context->Message_Block[63] = context->Length_Low;
|
||||
|
||||
SHA1ProcessMessageBlock(context);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* sha1.h
|
||||
*
|
||||
* Description:
|
||||
* This is the header file for code which implements the Secure
|
||||
* Hashing Algorithm 1 as defined in FIPS PUB 180-1 published
|
||||
* April 17, 1995.
|
||||
*
|
||||
* Many of the variable names in this code, especially the
|
||||
* single character names, were used because those were the names
|
||||
* used in the publication.
|
||||
*
|
||||
* Please read the file sha1.c for more information.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _SHA1_H_
|
||||
#define _SHA1_H_
|
||||
|
||||
#include <stdint.h>
|
||||
/*
|
||||
* If you do not have the ISO standard stdint.h header file, then you
|
||||
* must typdef the following:
|
||||
* name meaning
|
||||
* uint32_t unsigned 32 bit integer
|
||||
* uint8_t unsigned 8 bit integer (i.e., unsigned char)
|
||||
* int_least16_t integer of >= 16 bits
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _SHA_enum_
|
||||
#define _SHA_enum_
|
||||
enum
|
||||
{
|
||||
shaSuccess = 0,
|
||||
shaNull, /* Null pointer parameter */
|
||||
shaInputTooLong, /* input data too long */
|
||||
shaStateError /* called Input after Result */
|
||||
};
|
||||
#endif
|
||||
#define SHA1HashSize 20
|
||||
|
||||
/*
|
||||
* This structure will hold context information for the SHA-1
|
||||
* hashing operation
|
||||
*/
|
||||
typedef struct SHA1Context
|
||||
{
|
||||
uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
|
||||
|
||||
uint32_t Length_Low; /* Message length in bits */
|
||||
uint32_t Length_High; /* Message length in bits */
|
||||
|
||||
/* Index into message block array */
|
||||
int_least16_t Message_Block_Index;
|
||||
uint8_t Message_Block[64]; /* 512-bit message blocks */
|
||||
|
||||
int Computed; /* Is the digest computed? */
|
||||
int Corrupted; /* Is the message digest corrupted? */
|
||||
} SHA1Context;
|
||||
|
||||
/*
|
||||
* Function Prototypes
|
||||
*/
|
||||
|
||||
int SHA1Reset( SHA1Context *);
|
||||
int SHA1Input( SHA1Context *,
|
||||
const uint8_t *,
|
||||
unsigned int);
|
||||
int SHA1Result( SHA1Context *,
|
||||
uint8_t Message_Digest[SHA1HashSize]);
|
||||
|
||||
#endif
|
||||
@@ -113,7 +113,7 @@ void quisk_sample_level(const char * msg, complex double * cSamples, int nSample
|
||||
level = d;
|
||||
}
|
||||
if (QuiskTimeSec() - time0 > 0.1) {
|
||||
printf ("sample_level %s: %10.6lf count %8d\n", msg, level / scale, count);
|
||||
printf ("peak sample level %s: %14.6lf count %8d\n", msg, level / scale, count);
|
||||
level = 0;
|
||||
count = 0;
|
||||
time0 = QuiskTimeSec();
|
||||
@@ -1019,7 +1019,9 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
|
||||
// Play digital if required
|
||||
//if (is_DGT)
|
||||
play_sound_interface(&DigitalOutput, nSamples, cSamples, 1, digital_output_level);
|
||||
play_sound_interface(&DigitalOutput, nSamples, cSamples, 1, digital_output_level);
|
||||
// Send radio sound to TCI
|
||||
tci_send_audio(cSamples, nSamples);
|
||||
|
||||
// Perhaps record the speaker audio to a file
|
||||
if ( ! key_state && file_rec_audio.fp)
|
||||
@@ -1065,7 +1067,10 @@ int quisk_read_sound(void) // Called from sound thread
|
||||
for (i = 0; i < mic_count; i++)
|
||||
cSamples[i] = 0;
|
||||
}
|
||||
//quisk_sample_level("read mic or DGT", cSamples, mic_count, CLIP16);
|
||||
// Receive microphone sound from TCI
|
||||
//quisk_sample_level("read mic or DGT", cSamples, mic_count, CLIP32);
|
||||
mic_count = tci_get_mic(cSamples, mic_count);
|
||||
//quisk_sample_level("quisk_tci", cSamples, mic_count, CLIP32);
|
||||
// Perhaps record the microphone audio to the speaker audio file
|
||||
if (key_state && file_rec_audio.fp)
|
||||
quisk_record_audio(&file_rec_audio, cSamples, mic_count);
|
||||
|
||||
@@ -0,0 +1,651 @@
|
||||
#include <Python.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "ws.h"
|
||||
#include <pthread.h>
|
||||
#include <complex.h>
|
||||
#include <time.h>
|
||||
#include "quisk.h"
|
||||
|
||||
#define TCI_RX_BUF_SIZE 1024
|
||||
#define TCI_TX_BUFFER_MSEC 30 // Buffer delay in milliseconds
|
||||
#define TCI_COMMAND_SIZE 32
|
||||
|
||||
static int verbose; // non-zero for verbose log messages
|
||||
static int tci_port;
|
||||
|
||||
// Implement the ExpertSDR2 Version 1.4 protocol:
|
||||
// The only sample rate is 48000. The Tx audio parameters are copied from the Rx audio stream.
|
||||
// The only sample type is FLOAT32, type 4. The FLOAT32 type is 3 in ExpertSDR3. We will always use 3.
|
||||
// WSJT-X RX_AUDIO_STREAM must be two channels and Stream.length is the number of floats (not samples).
|
||||
// WSJT-X TX_AUDIO_STREAM always returns two channels but the data is one channel in half the data length.
|
||||
// It is unclear whether stream.length is the number of floats, or half that for two channels. And version 1.4 lacks stream.channels.
|
||||
// The protocol name for TCI version 1.4 is "protocol:ESDR,1.4;" and the port is 40001.
|
||||
// The protocol name for TCI version 2.0 is "protocol:ExpertSDR3,2.0;" and the port is 50001.
|
||||
// The TCI modulations are: AM, SAM, DSB, LSB, USB, CW, NFM, WFM, SPEC, DIGL, DIGU, DRM.
|
||||
|
||||
// These are TCI parameters:
|
||||
//static int quisk_dds;
|
||||
//static int quisk_if;
|
||||
static int quisk_vfo;
|
||||
static int client_vfo=-1;
|
||||
static int quisk_trx;
|
||||
static int client_trx=-1;
|
||||
static int quisk_split_enable;
|
||||
static int client_split_enable=-1;
|
||||
static char quisk_modulation[TCI_COMMAND_SIZE];
|
||||
static char client_modulation[TCI_COMMAND_SIZE]={0};
|
||||
|
||||
static ws_cli_conn_t tci_clients_list[MAX_CLIENTS]; // list of TCI clients
|
||||
static int tci_clients_count;
|
||||
ws_cli_conn_t tci_tx_audio_client; // single client providing Tx audio
|
||||
static int tci_started; // Did we start the TCI server?
|
||||
|
||||
static pthread_mutex_t clients_list_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t tx_buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
enum StreamType // ExpertSDR3
|
||||
{
|
||||
IQ_STREAM = 0, // Receiver IQ signal stream
|
||||
RX_AUDIO_STREAM = 1, // Receiver audio stream
|
||||
TX_AUDIO_STREAM = 2, // Audio stream for transmitter
|
||||
TX_CHRONO = 3, // Time marker flow for audio signal transmission
|
||||
LINEOUT_STREAM = 4 // Linear audio output audio stream
|
||||
} ;
|
||||
|
||||
enum SampleType // ExpertSDR3
|
||||
{
|
||||
TCI_INT16 = 0, // 16-bit integral character type
|
||||
TCI_INT24 = 1, // 24-bit integral character type
|
||||
TCI_INT32 = 2, // 32-bit integral character type
|
||||
TCI_FLOAT32 = 3, // 32-bit type with a floating point (was 4 in SDR2)
|
||||
} ;
|
||||
|
||||
struct ClientData { // data for each client
|
||||
char msg_buf[TCI_RX_BUF_SIZE];
|
||||
int msg_length;
|
||||
int send_Rx_audio_stream;
|
||||
int audio_stream_samplerate;
|
||||
int audio_stream_sample_type;
|
||||
int audio_stream_channels;
|
||||
int audio_stream_samples;
|
||||
int audio_stream_bytes_per_sample;
|
||||
} ;
|
||||
|
||||
struct _Stream {
|
||||
uint32_t receiver; // receiver number
|
||||
uint32_t sample_rate; // sampling rate
|
||||
uint32_t format; // sample type determined in SampleType
|
||||
uint32_t codec; // compression algorithm (not implemented), always 0
|
||||
uint32_t crc; // checksum (not implemented), always 0
|
||||
int32_t length; // number of samples
|
||||
uint32_t type; // stream type determined in StreamType
|
||||
uint32_t channels; // number of channels for ExpertSDR3
|
||||
uint32_t reserv[8]; // reserved
|
||||
uint8_t data[16384]; // samples
|
||||
} ;
|
||||
|
||||
struct _TxChrono {
|
||||
uint32_t receiver; // receiver number
|
||||
uint32_t sample_rate; // sampling rate
|
||||
uint32_t format; // sample type determined in SampleType
|
||||
uint32_t codec; // compression algorithm (not implemented), always 0
|
||||
uint32_t crc; // checksum (not implemented), always 0
|
||||
int32_t length; // number of samples
|
||||
uint32_t type; // stream type determined in StreamType
|
||||
uint32_t channels; // number of channels for ExpertSDR3
|
||||
uint32_t reserv[8]; // reserved
|
||||
} ;
|
||||
|
||||
static struct _TxChrono TxChrono = {
|
||||
.receiver = 0,
|
||||
.sample_rate = 48000,
|
||||
.format = TCI_FLOAT32,
|
||||
.codec = 0,
|
||||
.crc = 0,
|
||||
.length = 0,
|
||||
.type = TX_CHRONO,
|
||||
.channels = 1,
|
||||
.reserv = {0, 0, 0, 0, 0, 0, 0, 0}
|
||||
} ;
|
||||
|
||||
struct _StreamTypes {
|
||||
char * name;
|
||||
time_t when;
|
||||
} ;
|
||||
|
||||
#define NUM_STREAM_TYPES 5
|
||||
struct _StreamTypes StreamTypes[NUM_STREAM_TYPES] = {
|
||||
{"iq ", 0},
|
||||
{"rx_audio ", 0},
|
||||
{"tx_audio ", 0},
|
||||
{"tx_chrono", 0},
|
||||
{"lineout ", 0}
|
||||
} ;
|
||||
|
||||
static void PrintStream(const char * msg, size_t size)
|
||||
{
|
||||
struct _Stream * pt = (struct _Stream *)msg;
|
||||
|
||||
if (time(NULL) - StreamTypes[pt->type].when > 5) {
|
||||
StreamTypes[pt->type].when = time(NULL);
|
||||
if (pt->type == TX_AUDIO_STREAM)
|
||||
QuiskPrintf("TCI Receiving %slength %5d:", StreamTypes[pt->type].name, (int)size);
|
||||
else
|
||||
QuiskPrintf("TCI Sending %s length %5d:", StreamTypes[pt->type].name, (int)size);
|
||||
QuiskPrintf("%d %5d %d %d %d %5d %d %d\n", (int)(pt->receiver), (int)(pt->sample_rate), (int)(pt->format), (int)(pt->codec),
|
||||
(int)(pt->crc), (int)(pt->length), (int)(pt->type), (int)(pt->channels));
|
||||
}
|
||||
}
|
||||
|
||||
static int sendframe_txt(ws_cli_conn_t client, const char * msg)
|
||||
{
|
||||
if (verbose)
|
||||
QuiskPrintf("TCI Send text %s\n", msg);
|
||||
return ws_sendframe_txt(client, msg);
|
||||
}
|
||||
|
||||
static int sendframe_txt_bcast(uint16_t port, const char * msg)
|
||||
{
|
||||
if (verbose)
|
||||
QuiskPrintf("TCI Broadcast text %s\n", msg);
|
||||
return ws_sendframe_txt_bcast(port, msg);
|
||||
}
|
||||
|
||||
static size_t sendframe_bin(ws_cli_conn_t client, const char * msg, size_t size)
|
||||
{
|
||||
if (verbose)
|
||||
PrintStream(msg, size);
|
||||
return ws_sendframe_bin(client, msg, size);
|
||||
}
|
||||
|
||||
static int text_message(ws_cli_conn_t client, struct ClientData * ctx)
|
||||
{ // This function alters the message ctx->msg_buf.
|
||||
int i;
|
||||
char char_buf[TCI_COMMAND_SIZE];
|
||||
char * saveptr, * command;
|
||||
char * arg1, * arg2, * arg3;
|
||||
|
||||
if (verbose)
|
||||
QuiskPrintf("TCI Received text %s\n", ctx->msg_buf);
|
||||
for (i = 0; ctx->msg_buf[i]; i++)
|
||||
ctx->msg_buf[i] = tolower(ctx->msg_buf[i]);
|
||||
command = strtok_r(ctx->msg_buf, ":", &saveptr);
|
||||
if (command == NULL)
|
||||
return 0;
|
||||
arg1 = strtok_r(NULL, ",;", &saveptr);
|
||||
arg2 = strtok_r(NULL, ",;", &saveptr);
|
||||
arg3 = strtok_r(NULL, ",;", &saveptr);
|
||||
// The command will be broadcast to all clients unless the return value is zero.
|
||||
switch (command[0]) {
|
||||
case 'a':
|
||||
if (strcmp(command, "audio_start") == 0) {
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
for (i = 0; i < tci_clients_count; i++) {
|
||||
if (tci_clients_list[i] == client) {
|
||||
ctx->send_Rx_audio_stream = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
}
|
||||
else if (strcmp(command, "audio_stop") == 0) {
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
for (i = 0; i < tci_clients_count; i++) {
|
||||
if (tci_clients_list[i] == client) {
|
||||
ctx->send_Rx_audio_stream = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
}
|
||||
else if (strcmp(command, "audio_stream_sample_type") == 0) {
|
||||
if (strcmp(arg1, "float32") == 0) {
|
||||
ctx->audio_stream_sample_type = TCI_FLOAT32;
|
||||
ctx->audio_stream_bytes_per_sample = 4;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (strcmp(command, "audio_samplerate") == 0) {
|
||||
if (atoi(arg1) != 48000)
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "audio_stream_channels") == 0) {
|
||||
if (strcmp(arg1, "1") == 0)
|
||||
ctx->audio_stream_channels = 1;
|
||||
else if (strcmp(arg1, "2") == 0)
|
||||
ctx->audio_stream_channels = 2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "audio_stream_samples") == 0) {
|
||||
return 0; // do not send
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
if (strcmp(command, "iq_start") == 0)
|
||||
return 0; // do not send
|
||||
else if (strcmp(command, "iq_stop") == 0)
|
||||
return 0; // do not send
|
||||
else if (strcmp(command, "iq_samplerate") == 0)
|
||||
return 0; // do not send
|
||||
break;
|
||||
case 'm':
|
||||
if (strcmp(command, "modulation") == 0) {
|
||||
if (arg2) {
|
||||
for (i = 0; arg2[i]; i++)
|
||||
arg2[i] = toupper(arg2[i]);
|
||||
strncpy(client_modulation, arg2, TCI_COMMAND_SIZE - 1);
|
||||
}
|
||||
else {
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "modulation:0,%.9s;", quisk_modulation);
|
||||
sendframe_txt(client, char_buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (strcmp(command, "split_enable") == 0) {
|
||||
if (arg2) {
|
||||
if (strcmp(arg2, "true") == 0)
|
||||
client_split_enable = 1;
|
||||
else
|
||||
client_split_enable = 0;
|
||||
}
|
||||
else {
|
||||
if (quisk_split_enable)
|
||||
sendframe_txt(client, "split_enable:0,true;");
|
||||
else
|
||||
sendframe_txt(client, "split_enable:0,false;");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
if (strcmp(command, "trx") == 0) {
|
||||
if (arg2) {
|
||||
if (strcmp(arg2, "true") == 0) {
|
||||
if (quisk_trx == 0) {
|
||||
client_trx = 1;
|
||||
CircularBuffer(0, NULL, TCI_TX_BUFFER_MSEC * ctx->audio_stream_samplerate / 1000, 0);
|
||||
tci_tx_audio_client = client;
|
||||
}
|
||||
}
|
||||
else if (client == tci_tx_audio_client) {
|
||||
client_trx = 0;
|
||||
tci_tx_audio_client = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (quisk_trx)
|
||||
sendframe_txt(client, "trx:0,true;");
|
||||
else
|
||||
sendframe_txt(client, "trx:0,false;");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(command, "tx_stream_audio_buffering") == 0) {
|
||||
return 0; // do not send
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
if (strcmp(command, "vfo") == 0) {
|
||||
if (arg3) {
|
||||
client_vfo = atoi(arg3);
|
||||
}
|
||||
else {
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "vfo:0,0,%d;", quisk_vfo);
|
||||
sendframe_txt(client, char_buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 1; // send to all clients
|
||||
}
|
||||
|
||||
static void onopen(ws_cli_conn_t client)
|
||||
{
|
||||
char *cli;
|
||||
char command[TCI_COMMAND_SIZE];
|
||||
struct ClientData * ctx;
|
||||
|
||||
// Make parameters for each client:
|
||||
ctx = malloc(sizeof(struct ClientData));
|
||||
ctx->msg_length = 0;
|
||||
ctx->send_Rx_audio_stream = 0;
|
||||
ctx->audio_stream_samplerate = 48000;
|
||||
ctx->audio_stream_sample_type = TCI_FLOAT32;
|
||||
ctx->audio_stream_channels = 2;
|
||||
ctx->audio_stream_samples = 0;
|
||||
ctx->audio_stream_bytes_per_sample = 4;
|
||||
ws_set_connection_context(client, ctx);
|
||||
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
tci_clients_list[tci_clients_count++] = client;
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
|
||||
if (verbose) {
|
||||
cli = ws_getaddress(client);
|
||||
QuiskPrintf("TCI *Connection opened, addr: %s\n", cli);
|
||||
}
|
||||
|
||||
sendframe_txt(client, "protocol:ESDR,1.4;");
|
||||
sendframe_txt(client, "vfo_limits:30000,30000000;");
|
||||
sendframe_txt(client, "if_limits:-48000,48000;");
|
||||
sendframe_txt(client, "trx_count:1;");
|
||||
sendframe_txt(client, "channel_count:1;");
|
||||
sendframe_txt(client, "device:QuiskSDR;");
|
||||
sendframe_txt(client, "receive_only:false;");
|
||||
sendframe_txt(client, "modulations_list:USB,LSB,CW,AM,FM,DIGL,DIGU;");
|
||||
sendframe_txt(client, "ready;");
|
||||
|
||||
sendframe_txt(client, "start;");
|
||||
sendframe_txt(client, "tx_enable:0,true;");
|
||||
//snprintf(command, TCI_COMMAND_SIZE, "dds:0,%d;", quisk_dds);
|
||||
//sendframe_txt(client, command);
|
||||
//snprintf(command, TCI_COMMAND_SIZE, "if:0,0,%d;", quisk_if);
|
||||
//sendframe_txt(client, command);
|
||||
snprintf(command, TCI_COMMAND_SIZE, "modulation:0,%.9s;", quisk_modulation);
|
||||
sendframe_txt(client, command);
|
||||
snprintf(command, TCI_COMMAND_SIZE, "vfo:0,0,%d;", quisk_vfo);
|
||||
sendframe_txt(client, command);
|
||||
if (quisk_trx)
|
||||
sendframe_txt(client, "trx:0,true;");
|
||||
else
|
||||
sendframe_txt(client, "trx:0,false;");
|
||||
if (quisk_split_enable)
|
||||
sendframe_txt(client, "split_enable:0,true;");
|
||||
else
|
||||
sendframe_txt(client, "split_enable:0,false;");
|
||||
}
|
||||
|
||||
static void onclose(ws_cli_conn_t client)
|
||||
{
|
||||
int i, count;
|
||||
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
count = tci_clients_count;
|
||||
tci_clients_count = 0;
|
||||
for (i = 0; i < count; i++)
|
||||
if (tci_clients_list[i] != client)
|
||||
tci_clients_list[tci_clients_count++] = tci_clients_list[i];
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
|
||||
free(ws_get_connection_context(client));
|
||||
if (verbose) {
|
||||
char * cli = ws_getaddress(client);
|
||||
QuiskPrintf("TCI *Connection closed, addr: %s\n", cli);
|
||||
}
|
||||
}
|
||||
|
||||
static void onmessage(ws_cli_conn_t client, const unsigned char *msg, uint64_t size, int type)
|
||||
{
|
||||
char echo_copy[TCI_RX_BUF_SIZE];
|
||||
complex double sample;
|
||||
|
||||
if (type == WS_FR_OP_TXT) {
|
||||
if (size > TCI_RX_BUF_SIZE / 2)
|
||||
return; // prevent overflow
|
||||
struct ClientData * ctx = ws_get_connection_context(client);
|
||||
if (ctx->msg_length + size >= TCI_RX_BUF_SIZE - 2)
|
||||
ctx->msg_length = 0; // prevent overflow
|
||||
memcpy(ctx->msg_buf + ctx->msg_length, msg, size); // append msg to buffer
|
||||
ctx->msg_length += size;
|
||||
ctx->msg_buf[ctx->msg_length] = '\0';
|
||||
char * semicolon;
|
||||
char save;
|
||||
while ((semicolon = strchr(ctx->msg_buf, ';')) != NULL) {
|
||||
save = *(semicolon + 1);
|
||||
*(semicolon + 1) = '\0';
|
||||
strncpy(echo_copy, ctx->msg_buf, TCI_RX_BUF_SIZE);
|
||||
if (text_message(client, ctx)) // should we send to all clients?
|
||||
sendframe_txt_bcast(tci_port, echo_copy);
|
||||
*(semicolon + 1) = save;
|
||||
size_t processed_len = (semicolon - ctx->msg_buf) + 1;
|
||||
size_t remaining_len = ctx->msg_length - processed_len;
|
||||
memmove(ctx->msg_buf, ctx->msg_buf + processed_len, remaining_len);
|
||||
ctx->msg_length = remaining_len;
|
||||
ctx->msg_buf[ctx->msg_length] = '\0';
|
||||
}
|
||||
}
|
||||
else if (type == WS_FR_OP_BIN) {
|
||||
struct _Stream * pt = (struct _Stream *)msg;
|
||||
int count = 0;
|
||||
float re, im;
|
||||
int channels = TxChrono.channels;
|
||||
int two_channels = channels == 2;
|
||||
int header = 16 * sizeof(uint32_t);
|
||||
const void * vpt = msg + header;
|
||||
const void * vpt_end = vpt + (size - header);
|
||||
|
||||
if (pt->type == TX_AUDIO_STREAM) {
|
||||
if (pt->length > 0 && client == tci_tx_audio_client) {
|
||||
pthread_mutex_lock(&tx_buffer_mutex);
|
||||
switch (TxChrono.format) {
|
||||
case TCI_INT16:
|
||||
break;
|
||||
case TCI_FLOAT32: // FLOAT32 in version 2.0
|
||||
case 4: // FLOAT32 in version 1.4
|
||||
// Version 1.4 does not return the number of channels. We assume one channel.
|
||||
while (vpt < vpt_end && count < pt->length) {
|
||||
re = *(float *)vpt;
|
||||
vpt += sizeof(float);
|
||||
if (two_channels) {
|
||||
im = *(float *)vpt;
|
||||
vpt += sizeof(float);
|
||||
}
|
||||
else {
|
||||
im = re;
|
||||
}
|
||||
sample = (re + I * im) * (CLIP32 / 2);
|
||||
count += CircularBuffer(0, &sample, 0, 1);
|
||||
}
|
||||
if (verbose && count != pt->length)
|
||||
QuiskPrintf("TCI TX count %d %d\n", count, pt->length);
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&tx_buffer_mutex);
|
||||
}
|
||||
}
|
||||
if (verbose)
|
||||
PrintStream((const char *)msg, size);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void tci_startup(void)
|
||||
{
|
||||
struct ws_server tci = {
|
||||
.host = NULL,
|
||||
.port = 0,
|
||||
.thread_loop = 1,
|
||||
.timeout_ms = 1000,
|
||||
.evs.onopen = &onopen,
|
||||
.evs.onclose = &onclose,
|
||||
.evs.onmessage = &onmessage
|
||||
};
|
||||
char host32[32];
|
||||
|
||||
tci_clients_count = 0;
|
||||
tci_port = QuiskGetConfigInt("tci_port", 40001);
|
||||
strncpy(host32, QuiskGetConfigString("tci_ip", "127.0.0.1"), 31);
|
||||
tci.host = host32;
|
||||
tci.port = tci_port;
|
||||
ws_socket(&tci);
|
||||
if (verbose)
|
||||
QuiskPrintf("TCI Start server on host %s port %d\n", tci.host, tci.port);
|
||||
return;
|
||||
}
|
||||
|
||||
void tci_send_audio(complex double * cSamples, int nSamples) // called from the sound thread
|
||||
{
|
||||
int i, n;
|
||||
struct _Stream stream;
|
||||
struct ClientData * ctx;
|
||||
|
||||
if (tci_clients_count <= 0)
|
||||
return;
|
||||
|
||||
if (nSamples <= 0)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&clients_list_mutex);
|
||||
for (n = 0; n < tci_clients_count; n++) {
|
||||
ctx = ws_get_connection_context(tci_clients_list[n]);
|
||||
if (ctx->send_Rx_audio_stream == 0)
|
||||
continue;
|
||||
memset(&stream, 0, 16 * sizeof(uint32_t));
|
||||
stream.sample_rate = ctx->audio_stream_samplerate;
|
||||
stream.format = ctx->audio_stream_sample_type;
|
||||
stream.type = RX_AUDIO_STREAM;
|
||||
stream.channels = ctx->audio_stream_channels;
|
||||
int two_channels = stream.channels == 2;
|
||||
int bytes_per_sample = ctx->audio_stream_bytes_per_sample;
|
||||
void * vpt = &stream.data;
|
||||
void * vpt_end = vpt + 16384;
|
||||
switch (ctx->audio_stream_sample_type) {
|
||||
case TCI_FLOAT32:
|
||||
stream.length = 0; // make a frame
|
||||
for (i = 0; i < nSamples; i++) {
|
||||
*(float *)vpt = (float)(creal(cSamples[i]) / 2147483648);
|
||||
vpt += bytes_per_sample;
|
||||
stream.length++;
|
||||
if (two_channels) {
|
||||
*(float *)vpt = (float)(cimag(cSamples[i]) / 2147483648);
|
||||
vpt += bytes_per_sample;
|
||||
stream.length++;
|
||||
}
|
||||
if (vpt >= vpt_end || i == nSamples - 1) {
|
||||
sendframe_bin(tci_clients_list[n], (const char *)&stream,
|
||||
(16 * sizeof(uint32_t) + stream.length * bytes_per_sample));
|
||||
stream.length = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&clients_list_mutex);
|
||||
}
|
||||
|
||||
int tci_get_mic(complex double * cSamples, int mic_count) // called from the sound thread
|
||||
{
|
||||
if (tci_tx_audio_client) {
|
||||
TxChrono.length = mic_count;
|
||||
sendframe_bin(tci_tx_audio_client, (const char *)&TxChrono, 16 * sizeof(uint32_t));
|
||||
pthread_mutex_lock(&tx_buffer_mutex);
|
||||
mic_count = CircularBuffer(0, cSamples, mic_count, 0);
|
||||
pthread_mutex_unlock(&tx_buffer_mutex);
|
||||
}
|
||||
return mic_count;
|
||||
}
|
||||
|
||||
PyObject * quisk_tci_set_params(PyObject * self, PyObject * args, PyObject * keywds) // called from the GUI thread
|
||||
{ /* Call with keyword arguments ONLY.
|
||||
Sent from Quisk when parameters change. Broadcast the change to clients.*/
|
||||
static char * kwlist[] = {"start", "verbose", "tci_dds", "tci_if", "tci_vfo", "tci_trx", "tci_split_enable",
|
||||
"tci_modulation", NULL} ;
|
||||
int start=-1, new_dds=-1, new_if=-1, new_vfo=-1, new_trx=-1, new_split_enable=-1;
|
||||
char * mode=NULL;
|
||||
char char_buf[TCI_COMMAND_SIZE];
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiis", kwlist,
|
||||
&start, &verbose, &new_dds, &new_if, &new_vfo, &new_trx, &new_split_enable,
|
||||
&mode))
|
||||
return NULL;
|
||||
if (start == 1) {
|
||||
tci_started = 1;
|
||||
tci_startup(); // start the TCP server for TCI
|
||||
}
|
||||
if (tci_started == 0) {
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
if (new_vfo != -1) {
|
||||
quisk_vfo = new_vfo;
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "vfo:0,0,%d;", quisk_vfo);
|
||||
sendframe_txt_bcast(tci_port, char_buf);
|
||||
}
|
||||
if (new_split_enable != -1) {
|
||||
quisk_split_enable = new_split_enable;
|
||||
if (quisk_split_enable)
|
||||
sendframe_txt_bcast(tci_port, "split_enable:0,true;");
|
||||
else
|
||||
sendframe_txt_bcast(tci_port, "split_enable:0,false;");
|
||||
}
|
||||
if (new_trx != -1) {
|
||||
quisk_trx = new_trx;
|
||||
if (quisk_trx) {
|
||||
sendframe_txt_bcast(tci_port, "trx:0,true;");
|
||||
}
|
||||
else {
|
||||
tci_tx_audio_client = 0;
|
||||
sendframe_txt_bcast(tci_port, "trx:0,false;");
|
||||
}
|
||||
}
|
||||
if (mode) {
|
||||
if (strcmp(mode, "DGT-U") == 0)
|
||||
strcpy(quisk_modulation, "DIGU");
|
||||
else if (strcmp(mode, "DGT-L") == 0)
|
||||
strcpy(quisk_modulation, "DIGL");
|
||||
else if (strcmp(mode, "CWL") == 0 || strcmp(mode, "CWU") == 0)
|
||||
strcpy(quisk_modulation, "CW");
|
||||
else if (strcmp(mode, "LSB") == 0 || strcmp(mode, "USB") == 0 ||
|
||||
strcmp(mode, "AM") == 0 || strcmp(mode, "FM") == 0)
|
||||
strncpy(quisk_modulation, mode, TCI_COMMAND_SIZE - 1);
|
||||
else
|
||||
strcpy(quisk_modulation, "USB");
|
||||
snprintf(char_buf, TCI_COMMAND_SIZE, "modulation:0,%.9s;", quisk_modulation);
|
||||
sendframe_txt_bcast(tci_port, char_buf);
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject * quisk_tci_get_params(PyObject * self, PyObject * args) // called from the GUI thread
|
||||
{ /* Return client request for a parameter change */
|
||||
const char * name;
|
||||
int i;
|
||||
char char_buf[TCI_COMMAND_SIZE];
|
||||
|
||||
if (!PyArg_ParseTuple (args, "s", &name))
|
||||
return NULL;
|
||||
if (tci_started == 0) {
|
||||
}
|
||||
else if (strcmp(name, "tci_vfo") == 0 && client_vfo >= 0) {
|
||||
i = client_vfo;
|
||||
client_vfo = -1;
|
||||
return PyLong_FromLong(i);
|
||||
}
|
||||
else if (strcmp(name, "tci_split_enable") == 0 && client_split_enable >= 0) {
|
||||
i = client_split_enable;
|
||||
client_split_enable = -1;
|
||||
return PyLong_FromLong(i);
|
||||
}
|
||||
else if (strcmp(name, "tci_trx") == 0 && client_trx >= 0) {
|
||||
i = client_trx;
|
||||
client_trx = -1;
|
||||
return PyLong_FromLong(i);
|
||||
}
|
||||
else if (strcmp(name, "tci_modulation") == 0 && client_modulation[0] != 0) {
|
||||
char_buf[0] = 0;
|
||||
if (strcmp(client_modulation, "DIGU") == 0)
|
||||
strcpy(char_buf, "DGT-U");
|
||||
else if (strcmp(client_modulation, "DIGL") == 0)
|
||||
strcpy(char_buf, "DGT-L");
|
||||
else if (strcmp(client_modulation, "CW") == 0)
|
||||
strcpy(char_buf, "CWU");
|
||||
else if (strcmp(client_modulation, "LSB") == 0 || strcmp(client_modulation, "USB") == 0 ||
|
||||
strcmp(client_modulation, "AM") == 0 || strcmp(client_modulation, "FM") == 0)
|
||||
strncpy(char_buf, client_modulation, TCI_COMMAND_SIZE);
|
||||
client_modulation[0] = 0;
|
||||
if (char_buf[0])
|
||||
return PyString_FromString(char_buf);
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Amazing utf8 decoder & validator grabbed from:
|
||||
* http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*
|
||||
* All rights goes to the original author.
|
||||
*/
|
||||
|
||||
#include "utf8.h"
|
||||
|
||||
static const uint8_t utf8d[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
|
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
|
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
|
||||
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
|
||||
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
|
||||
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
|
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
|
||||
};
|
||||
|
||||
static
|
||||
uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
|
||||
uint32_t type = utf8d[byte];
|
||||
|
||||
*codep = (*state != UTF8_ACCEPT) ?
|
||||
(byte & 0x3fu) | (*codep << 6) :
|
||||
(0xff >> type) & (byte);
|
||||
|
||||
*state = utf8d[256 + *state*16 + type];
|
||||
return *state;
|
||||
}
|
||||
|
||||
int is_utf8(uint8_t *s) {
|
||||
uint32_t codepoint, state = 0;
|
||||
|
||||
while (*s)
|
||||
decode(&state, &codepoint, *s++);
|
||||
|
||||
return state == UTF8_ACCEPT;
|
||||
}
|
||||
|
||||
int is_utf8_len(uint8_t *s, size_t len) {
|
||||
uint32_t codepoint, state = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
decode(&state, &codepoint, *s++);
|
||||
|
||||
return state == UTF8_ACCEPT;
|
||||
}
|
||||
|
||||
uint32_t is_utf8_len_state(uint8_t *s, size_t len, uint32_t state) {
|
||||
uint32_t codepoint;
|
||||
size_t i;
|
||||
|
||||
codepoint = 0;
|
||||
for (i = 0; i < len; i++)
|
||||
decode(&state, &codepoint, *s++);
|
||||
|
||||
return state;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef UTF8_DECODE_H
|
||||
#define UTF8_DECODE_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* UTF8 return state. */
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
|
||||
extern int is_utf8(uint8_t* s);
|
||||
extern int is_utf8_len(uint8_t *s, size_t len);
|
||||
extern uint32_t is_utf8_len_state(uint8_t *s, size_t len, uint32_t state);
|
||||
|
||||
#endif
|
||||
@@ -321,3 +321,65 @@ PyObject * QuiskPrintf(char * format, ...)
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define MAX_CHANNELS 8
|
||||
int CircularBuffer(int channel, complex double * cSamples, int nRead, int nWrite)
|
||||
{
|
||||
// Initialize with cSamples NULL and nRead equal to the buffer delay in samples.
|
||||
// Read with nRead, write with nWrite. Only one can be non-zero.
|
||||
// Return the number of samples read or written.
|
||||
static struct _channel {
|
||||
complex double * cBuf; // circular sample buffer; save samples until the buffer fills to nDelay
|
||||
int sizeBuf; // current size of cBuf in complex samples
|
||||
int nDelay; // number of samples needed to start reading the buffer
|
||||
int nBuf; // current number of complex samples in cBuf
|
||||
int filling; // if true, we are filling the buffer and not returning samples
|
||||
int Windex; // index of write
|
||||
int Rindex; // index of read
|
||||
} Channels[MAX_CHANNELS] = {0};
|
||||
struct _channel * ptChannel = Channels + channel;
|
||||
int i, num;
|
||||
|
||||
if ( ! cSamples) { // initialize
|
||||
ptChannel->nDelay = nRead;
|
||||
ptChannel->sizeBuf = nRead * 8;
|
||||
ptChannel->cBuf = (complex double *)realloc(ptChannel->cBuf, ptChannel->sizeBuf * sizeof(complex double));
|
||||
ptChannel->filling = 1;
|
||||
ptChannel->Windex = 0;
|
||||
ptChannel->Rindex = 0;
|
||||
ptChannel->nBuf = 0;
|
||||
return 0;
|
||||
}
|
||||
if ( ! ptChannel->cBuf)
|
||||
return 0;
|
||||
num = 0; // number of samples read or written
|
||||
if (nWrite > 0) { // Copy samples from cSamples to cBuf
|
||||
for (i = 0; i < nWrite && ptChannel->nBuf < ptChannel->sizeBuf; i++) {
|
||||
ptChannel->cBuf[ptChannel->Windex++] = cSamples[i];
|
||||
if (ptChannel->Windex >= ptChannel->sizeBuf)
|
||||
ptChannel->Windex = 0;
|
||||
ptChannel->nBuf++;
|
||||
num++;
|
||||
}
|
||||
if (ptChannel->nBuf >= ptChannel->nDelay)
|
||||
ptChannel->filling = 0;
|
||||
}
|
||||
else if (nRead > 0) {
|
||||
if (ptChannel->filling) { // return zero-valued samples
|
||||
for (num = 0; num < nRead; num++)
|
||||
cSamples[num] = 0;
|
||||
}
|
||||
else { // Copy samples from cBuf to cSamples
|
||||
for (i = 0; i < nRead && ptChannel->nBuf > 0; i++) {
|
||||
cSamples[i] = ptChannel->cBuf[ptChannel->Rindex++];
|
||||
if (ptChannel->Rindex >= ptChannel->sizeBuf)
|
||||
ptChannel->Rindex = 0;
|
||||
ptChannel->nBuf--;
|
||||
num++;
|
||||
}
|
||||
if (ptChannel->nBuf <= 0)
|
||||
ptChannel->filling = 1;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2022 Davidson Francis <davidsondfgl@gmail.com>
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @dir include/
|
||||
* @brief wsServer include directory
|
||||
*
|
||||
* @file ws.h
|
||||
* @brief wsServer constants and functions.
|
||||
*/
|
||||
#ifndef WS_H
|
||||
#define WS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
/**
|
||||
* @name Global configurations
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Max clients connected simultaneously.
|
||||
*/
|
||||
#ifndef MAX_CLIENTS
|
||||
#define MAX_CLIENTS 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Key and message configurations.
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Message buffer length.
|
||||
*/
|
||||
#define MESSAGE_LENGTH 2048
|
||||
/**
|
||||
* @brief Maximum frame/message length.
|
||||
*/
|
||||
#define MAX_FRAME_LENGTH (16*1024*1024)
|
||||
/**
|
||||
* @brief WebSocket key length.
|
||||
*/
|
||||
#define WS_KEY_LEN 24
|
||||
/**
|
||||
* @brief Magic string length.
|
||||
*/
|
||||
#define WS_MS_LEN 36
|
||||
/**
|
||||
* @brief Accept message response length.
|
||||
*/
|
||||
#define WS_KEYMS_LEN (WS_KEY_LEN + WS_MS_LEN)
|
||||
/**
|
||||
* @brief Magic string.
|
||||
*/
|
||||
#define MAGIC_STRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @name Handshake constants.
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Alias for 'Sec-WebSocket-Key'.
|
||||
*/
|
||||
#define WS_HS_REQ "Sec-WebSocket-Key"
|
||||
|
||||
/**
|
||||
* @brief Handshake accept message length.
|
||||
*/
|
||||
#define WS_HS_ACCLEN 130
|
||||
|
||||
/**
|
||||
* @brief Handshake accept message.
|
||||
*/
|
||||
#define WS_HS_ACCEPT \
|
||||
"HTTP/1.1 101 Switching Protocols\r\n" \
|
||||
"Upgrade: websocket\r\n" \
|
||||
"Connection: Upgrade\r\n" \
|
||||
"Sec-WebSocket-Accept: "
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @name Frame types.
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Frame FIN.
|
||||
*/
|
||||
#define WS_FIN 128
|
||||
|
||||
/**
|
||||
* @brief Frame FIN shift
|
||||
*/
|
||||
#define WS_FIN_SHIFT 7
|
||||
|
||||
/**
|
||||
* @brief Continuation frame.
|
||||
*/
|
||||
#define WS_FR_OP_CONT 0
|
||||
|
||||
/**
|
||||
* @brief Text frame.
|
||||
*/
|
||||
#define WS_FR_OP_TXT 1
|
||||
|
||||
/**
|
||||
* @brief Binary frame.
|
||||
*/
|
||||
#define WS_FR_OP_BIN 2
|
||||
|
||||
/**
|
||||
* @brief Close frame.
|
||||
*/
|
||||
#define WS_FR_OP_CLSE 8
|
||||
|
||||
/**
|
||||
* @brief Ping frame.
|
||||
*/
|
||||
#define WS_FR_OP_PING 0x9
|
||||
|
||||
/**
|
||||
* @brief Pong frame.
|
||||
*/
|
||||
#define WS_FR_OP_PONG 0xA
|
||||
|
||||
/**
|
||||
* @brief Unsupported frame.
|
||||
*/
|
||||
#define WS_FR_OP_UNSUPPORTED 0xF
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @name Close codes
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Normal close
|
||||
*/
|
||||
#define WS_CLSE_NORMAL 1000
|
||||
/**
|
||||
* @brief Protocol error
|
||||
*/
|
||||
#define WS_CLSE_PROTERR 1002
|
||||
/**@}*/
|
||||
/**
|
||||
* @brief Inconsistent message (invalid utf-8)
|
||||
*/
|
||||
#define WS_CLSE_INVUTF8 1007
|
||||
|
||||
/**
|
||||
* @name Connection states
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Connection not established yet.
|
||||
*/
|
||||
#define WS_STATE_CONNECTING 0
|
||||
/**
|
||||
* @brief Communicating.
|
||||
*/
|
||||
#define WS_STATE_OPEN 1
|
||||
/**
|
||||
* @brief Closing state.
|
||||
*/
|
||||
#define WS_STATE_CLOSING 2
|
||||
/**
|
||||
* @brief Closed.
|
||||
*/
|
||||
#define WS_STATE_CLOSED 3
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @name Timeout util
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Nanoseconds macro converter
|
||||
*/
|
||||
#define MS_TO_NS(x) ((x)*1000000)
|
||||
/**
|
||||
* @brief Timeout in milliseconds.
|
||||
*/
|
||||
#define TIMEOUT_MS (500)
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @name Handshake constants.
|
||||
*/
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief Debug
|
||||
*/
|
||||
#ifdef VERBOSE_MODE
|
||||
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG(...)
|
||||
#endif
|
||||
/**@}*/
|
||||
|
||||
#ifndef AFL_FUZZ
|
||||
#define SEND(client,buf,len) send_all((client), (buf), (len), MSG_NOSIGNAL)
|
||||
#define RECV(fd,buf,len) recv((fd)->client_sock, (buf), (len), 0)
|
||||
#else
|
||||
#define SEND(client,buf,len) write(fileno(stdout), (buf), (len))
|
||||
#define RECV(fd,buf,len) read((fd)->client_sock, (buf), (len))
|
||||
#endif
|
||||
|
||||
/* Opaque client connection type. */
|
||||
typedef uint64_t ws_cli_conn_t;
|
||||
|
||||
/* Opaque server instance type. */
|
||||
typedef struct ws_server ws_server_t;
|
||||
|
||||
/**
|
||||
* @brief Get server context.
|
||||
* Set when initializing `.context` in `struct ws_server`.
|
||||
*/
|
||||
void *ws_get_server_context(ws_cli_conn_t client);
|
||||
|
||||
/**
|
||||
* @brief Set connection context.
|
||||
*/
|
||||
void ws_set_connection_context(ws_cli_conn_t client, void *ptr);
|
||||
|
||||
/**
|
||||
* @brief Get connection context.
|
||||
*/
|
||||
void *ws_get_connection_context(ws_cli_conn_t client);
|
||||
|
||||
/**
|
||||
* @brief events Web Socket events types.
|
||||
*/
|
||||
struct ws_events
|
||||
{
|
||||
/**
|
||||
* @brief On open event, called when a new client connects.
|
||||
*/
|
||||
void (*onopen)(ws_cli_conn_t client);
|
||||
/**
|
||||
* @brief On close event, called when a client disconnects.
|
||||
*/
|
||||
void (*onclose)(ws_cli_conn_t client);
|
||||
/**
|
||||
* @brief On message event, called when a client sends a text
|
||||
* or binary message.
|
||||
*/
|
||||
void (*onmessage)(ws_cli_conn_t client,
|
||||
const unsigned char *msg, uint64_t msg_size, int type);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief server Web Socket server parameters
|
||||
*/
|
||||
struct ws_server
|
||||
{
|
||||
/**
|
||||
* @brief Required hostname that the wsServer will bind to
|
||||
*/
|
||||
const char *host;
|
||||
/**
|
||||
* @brief Listening port
|
||||
*/
|
||||
uint16_t port;
|
||||
/**
|
||||
* @brief Whether if the ws_socket() should create a new thread
|
||||
* and be non-blocking (1) or not (0).
|
||||
*/
|
||||
int thread_loop;
|
||||
/**
|
||||
* @brief Ping timeout in milliseconds
|
||||
*/
|
||||
uint32_t timeout_ms;
|
||||
/**
|
||||
* @brief Server events.
|
||||
*/
|
||||
struct ws_events evs;
|
||||
/**
|
||||
* @brief Server context.
|
||||
* Provided by the user, can be accessed via `ws_get_server_context` from `onopen`.
|
||||
*/
|
||||
void* context;
|
||||
};
|
||||
|
||||
/* Forward declarations. */
|
||||
|
||||
/* Internal usage. */
|
||||
extern int get_handshake_accept(char *wsKey, unsigned char **dest);
|
||||
extern int get_handshake_response(char *hsrequest, char **hsresponse);
|
||||
|
||||
/* External usage. */
|
||||
extern char *ws_getaddress(ws_cli_conn_t client);
|
||||
extern char *ws_getport(ws_cli_conn_t client);
|
||||
extern int ws_sendframe(
|
||||
ws_cli_conn_t client, const char *msg, uint64_t size, int type);
|
||||
extern int ws_sendframe_bcast(
|
||||
uint16_t port, const char *msg, uint64_t size, int type);
|
||||
extern int ws_sendframe_txt(ws_cli_conn_t client, const char *msg);
|
||||
extern int ws_sendframe_txt_bcast(uint16_t port, const char *msg);
|
||||
extern int ws_sendframe_bin(ws_cli_conn_t client, const char *msg,
|
||||
uint64_t size);
|
||||
extern int ws_sendframe_bin_bcast(uint16_t port, const char *msg,
|
||||
uint64_t size);
|
||||
extern int ws_get_state(ws_cli_conn_t client);
|
||||
extern int ws_close_client(ws_cli_conn_t client);
|
||||
extern int ws_socket(struct ws_server *ws_srv);
|
||||
|
||||
/* Ping routines. */
|
||||
extern void ws_ping(ws_cli_conn_t cid, int threshold);
|
||||
|
||||
#ifdef AFL_FUZZ
|
||||
extern int ws_file(struct ws_events *evs, const char *file);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WS_H */
|
||||
Reference in New Issue
Block a user