7 Commits
Author SHA1 Message Date
James C. Ahlstrom b45a71b017 Quisk version 4.2.52 2026-05-30 11:18:52 -04:00
James C. Ahlstrom e6b9d9dd05 Quisk version 4.2.51 2026-05-10 15:22:33 -04:00
James C. Ahlstrom 1f68d05cd5 Quisk version 4.2.50 2025-12-29 14:01:36 -05:00
James C. Ahlstrom 018fc0e871 Quisk version 4.2.49 2025-12-22 15:12:47 -05:00
James C. Ahlstrom 1111d24333 Quisk version 4.2.48 2025-11-24 12:28:01 -05:00
James C. Ahlstrom d8aee9284d Quisk version 4.2.47 2025-11-22 13:39:10 -05:00
James C. Ahlstrom 7121dacbbd Quisk version 4.2.46 2025-10-12 12:43:21 -04:00
34 changed files with 4836 additions and 339 deletions
+58
View File
@@ -1,3 +1,61 @@
Quisk Version 4.2.52 May 2026
==============================
This is a fix for various TCI bugs. Please test with WSJT-X and let me know if all problems are solved.
It is unlikely that other digital software will connect without problems, but please test and let me know.
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
AttributeError: 'Hardware' object has no attribute 'is_HermesLite2'
Quisk Version 4.2.49 December 2025
===================================
This version adds more features for the Anan 10E and other OpenHPSDR radios. Please test.
I added the forward power, SWR, temperature and PA current display to the OpenHPSDR radio. But
I don't have that radio so I need some help. I think the forward and reverse power connections
are correct, but it is still necessary to create a table to convert analog input codes to power.
Go to the Hardware screen and check the "Power meter calibration" setting. You will need to enter
"New" and create a suitable table. To see the ADC values, choose table "Show ADC".
There is no protocol standard to specify which analog input is used for temperature and PA current.
And AFAIK the Anan 10E has no such sensors. I am hoping to use the board ID to find the right input.
The ADC conversion is linear and the two points are specified on the Hardware screen.
I currently assigned the analog inputs as follows:
AIN1 Forward power
AIN2 Reverse power
AIN3 PA current
AIN5 Temperature
If you need to see the ADC codes 0-4095 for each input, go to hermes/quisk_hardware.py and
change DEBUG_SENSORS to one.
Quisk Version 4.2.48 November 2025
===================================
This is a quisk fix for a bug in the new OpenHPSDR radios. It fixes use_rx_udp that was reset to zero.
Quisk Version 4.2.47 November 2025
===================================
To improve the performance of Quisk with the Anan 10-E and other OpenHPSDR radios, I added
a new type of radio "OpenHPSDR". Use this type for the Anan 10-E and similar radios. It removes
all the protocol extensions added by the Hermes-Lite2 project. I removed the configuration
option "Hermes Lite2 protocol enable". This is now automatic.
I added buttons for antennas 0, 1, 2 and 3 for OpenHPSDR radios that support more antennas.
I added controls for the OpenHPSDR preamplifier and attenuator.
I do not have any Apache Labs radios, so I must depend on my users to report problems and solutions.
Quisk Version 4.2.46 October 2025
==================================
I fixed a bug in Hamlib rig 2 which happens with the new Pure Signal code. The Hamlib "f" command
now always returns the main receiver frequency, not one of the added receiver frequencies. This is
consistent with the Flex ZZ commands.
Quisk Version 4.2.45 October 2025
==================================
The new FreeDV including RADE mode is now a built-in mode of Quisk. Just press "FDV" to talk FreeDV.
-21
View File
@@ -1,21 +0,0 @@
# Quisk Software Defined Radio by N2ADR
**December 27, 2023**
This is Quisk, a Software Defined Radio (SDR).
You supply radio hardware such as Hermes Lite2 or SoftRock to convert
the antenna voltage to I/Q samples.
Then send the samples to a computer running Quisk.
The Quisk software will read the I/Q data, tune it, filter it,
demodulate it, and send the audio to headphones or speakers.
Quisk has a microphone input and a key input so it can operate as a
complete transceiver.
The web page for this project is [https://groups.io/g/n2adr-sdr](https://groups.io/g/n2adr-sdr).
The change log is [here](CHANGELOG.txt).
Installation instructions are [here](http://james.ahlstrom.name/quisk/docs.html#Installation).
The documentation is [here](http://james.ahlstrom.name/quisk/docs.html).
The help file is [here](http://james.ahlstrom.name/quisk/help.html).
+2 -2
View File
@@ -1,2 +1,2 @@
#Quisk version 4.2.45
from .quisk import main
#Quisk version 4.2.52
from .quisk import main
+155
View File
@@ -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;
}
+19
View File
@@ -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 */
+21 -25
View File
@@ -88,6 +88,7 @@ class Configuration:
platform_ignore = 'win_'
self.sections = []
self.receiver_data = []
self.receiver_explain = []
self.StatePath = conf.settings_file_path
if not self.StatePath:
self.StatePath = os.path.join(app.QuiskFilesDir, "quisk_settings.json")
@@ -158,9 +159,9 @@ class Configuration:
dlg.Destroy()
else:
radio_dict["use_soapy"] = '0'
if radio_type not in ("HiQSDR", "Hermes", "Red Pitaya", "Odyssey", "Odyssey2"):
if radio_type not in ("HiQSDR", "Hermes", "Red Pitaya", "Odyssey", "Odyssey2", "OpenHPSDR"):
radio_dict["use_rx_udp"] = '0'
if radio_type in ("Hermes", "Red Pitaya", "Odyssey2"):
if radio_type in ("Hermes", "Red Pitaya", "Odyssey2", "OpenHPSDR"):
if "Hermes_BandDict" not in radio_dict:
radio_dict["Hermes_BandDict"] = {}
if "Hermes_BandDictTx" not in radio_dict:
@@ -601,6 +602,7 @@ class Configuration:
# Receiver sections start with 16 #, "Receivers ", receiver name, explain
# self.receiver_data is a list of [receiver_name, receiver_data]
# receiver_data is a list of [data_name, text, fmt, help_text, values]
# self.receiver_explain is a list of the text in Receivers: the name, a comma and the explanation.
# Variable names start with ## variable_name variable_text, format
# The format is integer, number, text, boolean, integer choice, text choice, rfile
@@ -638,6 +640,7 @@ class Configuration:
continue
if line[0:27] == '################ Receivers ':
section = 'Receivers'
self.receiver_explain.append(line[27:].strip())
args = line[27:].split(',', 1)
rxname = args[0].strip()
section_data = []
@@ -2298,8 +2301,8 @@ class ControlMixin:
if self.radio_name == Settings[1]: # changed for current radio
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec', 'hermes_lite2_enable',
'fixed_tune_offset'):
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec',
'fixed_tune_offset', 'calibrate_temp_20', 'calibrate_temp_40', 'calibrate_current_0', 'calibrate_current_1'):
setattr(conf, name, x)
application.ImmediateChange(name)
elif name[0:4] in ('lin_', 'win_'):
@@ -2400,6 +2403,7 @@ class BaseWindow(wx.ScrolledWindow, ControlMixin):
def __init__(self, parent):
wx.ScrolledWindow.__init__(self, parent)
ControlMixin.__init__(self, parent)
self.SetScrollRate(8, 8)
class ConfigConfig(BaseWindow):
def __init__(self, parent, width):
@@ -2510,7 +2514,6 @@ The CW message will then repeat. A time of zero means no repeat.'
sl, btn = self.AddTextSliderHelp(4, " Repeat secs %.1f ", 0, 0, 100, self.OnPlayFileRepeat, help_text, span=2, scale=0.1)
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
def MakeFileButton(self, text, path, index, name, help_text):
if index < 10: # record buttons
cb = self.AddCheckBox(4, text, self.OnCheckRecPlay)
@@ -2688,7 +2691,6 @@ class ConfigTxAudio(BaseWindow):
application.CtrlTxAudioPreemph = sld
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
def OnTimer(self, event):
if not self.tmp_playing:
self.timer.Stop()
@@ -2737,8 +2739,8 @@ class Radios(BaseWindow): # The "Radios" first-level page
self.NextRow()
rb = self.AddRadioButton(1, "Add a new radio with the general type", self.NewTop)
choices = []
for name, data in local_conf.receiver_data:
choices.append(name)
for explain in local_conf.receiver_explain:
choices.append(explain)
choices.sort()
self.add_type = self.AddComboCtrl(2, '', choices=choices, no_edit=True)
self.add_type.SetSelection(0)
@@ -2766,7 +2768,6 @@ class Radios(BaseWindow): # The "Radios" first-level page
self.apply_btn = self.AddPushButton(3, "Apply", self.OnBtnApply)
self.NewRadioNames()
self.NewTop()
self.SetScrollRate(1, 1)
def NewTop(self, event=None):
if event:
btn = event.GetEventObject()
@@ -2810,7 +2811,7 @@ class Radios(BaseWindow): # The "Radios" first-level page
if not name or self.DuplicateName(name):
return
self.add_name.SetValue('')
typ = self.add_type.GetValue().strip()
typ = self.add_type.GetValue().split(',')[0].strip()
if local_conf.AddRadio(name, typ):
self.NewRadioNames()
local_conf.settings_changed = True
@@ -3003,7 +3004,6 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
self.AddColSpacer(2, 20)
self.AddColSpacer(5, 20)
self.FitInside()
self.SetScrollRate(1, 1)
def OnButtonChangeFavorites(self, event):
if isinstance(event, (ChoiceCombo, ComboCtrl)):
path = event.GetValue()
@@ -3128,7 +3128,6 @@ class RadioHardwareBase(BaseWindow): # The Hardware page in the second-level no
self.NextRow(7)
self.AddColSpacer(2, 20)
self.AddColSpacer(5, 20)
self.SetScrollRate(1, 1)
def OnButtonChangeHardware(self, event):
if isinstance(event, (ChoiceCombo, ComboCtrl)):
path = event.GetValue()
@@ -3175,25 +3174,24 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
data_names = local_conf.GetReceiverData(radio_type)
col = 1
border = 2
hermes_board_id = 0
if radio_type == "Hermes":
try:
hermes_board_id = application.Hardware.hermes_board_id
except:
pass
try:
is_HermesLite2 = application.Hardware.is_HermesLite2
except:
is_HermesLite2 = False
if radio_name == Settings[1] and hasattr(application.Hardware, "ProgramGateware"):
help_text = "Choose an RBF file and program the Gateware (FPGA software) over Ethernet."
self.AddTextButtonHelp(1, "Gateware Update", "Program from RBF file..", application.Hardware.ProgramGateware, help_text)
col = 1
self.NextRow(self.row + 2)
for name, text, fmt, help_text, values in data_names:
text = text.replace("\\u2103", "\u2103")
if name in ('hardware_file_name', 'widgets_file_name'):
pass
elif name[0:4] == platform_ignore:
pass
elif name in ('Hermes_BandDictEnTx', ):
pass
elif 'Hl2_' in name and hermes_board_id != 6:
elif 'Hl2_' in name and not is_HermesLite2:
pass
elif fmt[0:4] in ('dict', 'list'):
pass
@@ -3222,7 +3220,7 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
col = 1
border = 2
self.NextRow()
if hermes_board_id == 6:
if is_HermesLite2:
if col == 4:
self.NextRow()
help_text = ('This controls the bias level for transistors in the final power amplifier. Enter a level from 0 to 255.'
@@ -3238,7 +3236,6 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
self.HermesWriteBiasButton = self.AddPushButton(7, "Write", self.OnButtonHermesWriteBias, border=0)
self.HermesWriteBiasButton.Enable(enbl)
self.FitInside()
self.SetScrollRate(1, 1)
def OnHermesChangeBias0(self, event):
value = self.HermesBias0.GetValue()
application.Hardware.ChangeBias0(value)
@@ -3852,7 +3849,6 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
break
self.gbs = gbs
self.FitInside()
self.SetScrollRate(1, 1)
def ItemValue(self, row, col):
data_name = self.sound_names[row][col]
if col == 4: # Device names
@@ -4080,7 +4076,6 @@ The Tx bits are sent as the "Alex" filters and are sent to the J16 interface.'
self.AddBitField(col + 1, 7, 'Hermes_BandDictTx', band, bus, self.ChangeIO)
self.NextRow()
self.FitInside()
self.SetScrollRate(1, 1)
def SortCmp(self, item1):
# Numerical conversion of band name to megahertz
try:
@@ -4208,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.')
@@ -4255,7 +4253,6 @@ class RadioRemote(BaseWindow): # The Remote page in the second-level notebook f
self.AddColSpacer(3, 20)
self.AddColSpacer(6, 20)
self.FitInside()
self.SetScrollRate(1, 1)
def OnChangePassword(self, event):
text = local_conf.globals.get('remote_radio_password', '')
dlg = wx.TextEntryDialog(self, "Please enter a password for remote access",
@@ -4337,7 +4334,6 @@ class xxRadioFilters(BaseWindow): # The Filters page in the second-level notebo
self.NextRow()
index += 1
self.FitInside()
self.SetScrollRate(1, 1)
def OnChangeFreq(self, event):
freq = event.GetString()
radio_dict = local_conf.GetRadioDict(self.radio_name)
+29 -2
View File
@@ -377,12 +377,13 @@ python3 quisk.py
<br>
<p>
If "make" fails, you probably have missing packages or missing "-dev" packages. Try to figure out what is missing from the error messages.
You only clone github for the initial installation. To update Quisk, use pull:
You only clone github for the initial installation. To update Quisk, use pull followed by make:
</p>
<br>
<pre>
cd ~/quisk
git pull
make
</pre>
<br>
<p>
@@ -1433,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
View File
@@ -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);
}
+157
View File
@@ -0,0 +1,157 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
import wx, math
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
self.start_row = app.widget_row # The first available row
self.start_col = app.button_start_col # The start of the button columns
self.num_rows_added = 1
self.btnPreamp = app.QuiskCheckbutton(frame, self.OnBtnPreamp, text='Preamp')
gbs.Add(self.btnPreamp, (self.start_row, self.start_col), flag=wx.EXPAND)
bw, bh = self.btnPreamp.GetMinSize()
init = app.hermes_atten_dB
self.sliderAtten = app.SliderBoxHH(frame, 'Atten %d dB', init, 0, 31, self.OnAtten, True)
self.sliderAtten.idName = "RfAtten"
app.midiControls["RfAtten"] = (self.sliderAtten, self.OnAtten)
hardware.ChangeAtten(init)
gbs.Add(self.sliderAtten, (self.start_row, self.start_col + 2), (1, 6), flag=wx.EXPAND)
if conf.button_layout == "Small screen":
# Display four data items in a single window
self.text_temperature = app.QuiskText1(frame, '', bh)
self.text_pa_current = app.QuiskText1(frame, '', bh)
self.text_fwd_power = app.QuiskText1(frame, '', bh)
self.text_swr = app.QuiskText1(frame, '', bh)
self.text_data = self.text_temperature
self.text_pa_current.Hide()
self.text_fwd_power.Hide()
self.text_swr.Hide()
b = app.QuiskPushbutton(frame, self.OnTextDataMenu, '..')
szr = self.data_sizer = wx.BoxSizer(wx.HORIZONTAL)
szr.Add(self.text_data, 1, flag=wx.ALIGN_CENTER_VERTICAL)
szr.Add(b, 0, flag=wx.ALIGN_CENTER_VERTICAL)
gbs.Add(szr, (self.start_row, self.start_col + 10), (1, 2), flag=wx.EXPAND)
# Make a popup menu for the data window
self.text_data_menu = wx.Menu()
item = self.text_data_menu.Append(-1, 'Temperature')
app.Bind(wx.EVT_MENU, self.OnDataTemperature, item)
item = self.text_data_menu.Append(-1, 'PA Current')
app.Bind(wx.EVT_MENU, self.OnDataPaCurrent, item)
item = self.text_data_menu.Append(-1, 'Fwd Power')
app.Bind(wx.EVT_MENU, self.OnDataFwdPower, item)
item = self.text_data_menu.Append(-1, 'SWR')
app.Bind(wx.EVT_MENU, self.OnDataSwr, item)
else:
szr = wx.BoxSizer(wx.HORIZONTAL)
gbs.Add(szr, (self.start_row, self.start_col + 10), (1, 18), flag=wx.EXPAND)
text_temperature = wx.StaticText(frame, -1, ' Temp 100DC XX', style=wx.ST_NO_AUTORESIZE)
size = text_temperature.GetBestSize()
text_temperature.Destroy()
self.text_temperature = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_pa_current = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_fwd_power = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
self.text_swr = wx.StaticText(frame, -1, '', size=size, style=wx.ST_NO_AUTORESIZE)
flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL
szr.Add(self.text_temperature, 0, flag=flag)
szr.Add(self.text_pa_current, 0, flag=flag)
szr.Add(self.text_fwd_power, 0, flag=flag)
szr.Add(self.text_swr, 0, flag=flag)
def OnBtnPreamp(self, event):
self.hardware.ChangePreamp(self.btnPreamp.GetValue())
def OnAtten(self, event):
value = self.sliderAtten.GetValue()
self.hardware.ChangeAtten(value)
self.application.hermes_atten_dB = value
def Code2FwdRevWatts(self, fwd, rev): # Convert the fwd/rev power code to watts forward and reverse
#print (self.hardware.hermes_rev_power, self.hardware.hermes_fwd_power)
fwd = self.hardware.InterpolatePower(fwd)
rev = self.hardware.InterpolatePower(rev)
# Which voltage is forward and reverse depends on the polarity of the current sense transformer
if fwd >= rev:
return fwd, rev
else:
return rev, fwd
def UpdateText(self): # Called at intervals
conf = self.config
# Temperature
temp = self.hardware.hermes_temperature
if conf.calibrate_temp_20 == conf.calibrate_temp_40:
temp = " Temp ADC %4d" % temp
else:
m = 20.0 / (conf.calibrate_temp_40 - conf.calibrate_temp_20)
b = 20.0 - m * conf.calibrate_temp_20
temp = m * temp + b
temp = (" Temp %3.0f" % temp) + u'\u2103'
self.text_temperature.SetLabel(temp)
# power amp current
current = self.hardware.hermes_pa_current
if conf.calibrate_current_0 == conf.calibrate_current_1:
current = " Amps ADC %4d" % current
else:
m = 1.0 / (conf.calibrate_current_1 - conf.calibrate_current_0)
b = - m * conf.calibrate_current_0
current = m * current + b
current = " PA %4.0f ma" % (1000*current)
self.text_pa_current.SetLabel(current)
# forward and reverse peak power
if not self.hardware.power_interpolator: # For no interpolator, show ADC
fwd = self.hardware.hermes_fwd_power
rev = self.hardware.hermes_rev_power
self.text_fwd_power.SetLabel("Fwd ADC %4d" % fwd)
self.text_swr.SetLabel("Rev ADC %4d" % rev)
return
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_peak, self.hardware.hermes_rev_peak)
# forward less reverse power
power = fwd - rev
if power < 0.0:
power = 0.0
text = " PEP %3.1f watts" % power
self.text_fwd_power.SetLabel(text)
# SWR based on average power
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_power, self.hardware.hermes_rev_power)
if fwd >= 0.05:
gamma = math.sqrt(rev / fwd)
if gamma < 0.98:
swr = (1.0 + gamma) / (1.0 - gamma)
else:
swr = 99.0
if swr < 9.95:
text = " SWR %4.2f" % swr
else:
text = " SWR %4.0f" % swr
else:
text = " SWR ---"
self.text_swr.SetLabel(text)
def OnTextDataMenu(self, event):
btn = event.GetEventObject()
btn.PopupMenu(self.text_data_menu, (0,0))
def OnDataTemperature(self, event):
self.data_sizer.Replace(self.text_data, self.text_temperature)
self.text_data.Hide()
self.text_data = self.text_temperature
self.text_data.Show()
self.data_sizer.Layout()
def OnDataPaCurrent(self, event):
self.data_sizer.Replace(self.text_data, self.text_pa_current)
self.text_data.Hide()
self.text_data = self.text_pa_current
self.text_data.Show()
self.data_sizer.Layout()
def OnDataFwdPower(self, event):
self.data_sizer.Replace(self.text_data, self.text_fwd_power)
self.text_data.Hide()
self.text_data = self.text_fwd_power
self.text_data.Show()
self.data_sizer.Layout()
def OnDataSwr(self, event):
self.data_sizer.Replace(self.text_data, self.text_swr)
self.text_data.Hide()
self.text_data = self.text_swr
self.text_data.Show()
self.data_sizer.Layout()
+43 -15
View File
@@ -15,6 +15,7 @@ from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 0
DEBUG_I2C = 0
DEBUG_SENSORS = 0
class IOBoard:
"This class controls the N2ADR IO Board for the HermesLite 2"
@@ -46,6 +47,8 @@ class IOBoard:
self.old_receive = None
self.slow = 0
def HeartBeat(self): # Called at 10 Hz for housekeeping tasks
if not self.hardware.is_HermesLite2:
return
if not QS.get_params('rx_udp_started'):
return
if self.have_IO_Board is None:
@@ -154,6 +157,7 @@ class Hardware(BaseHardware):
self.hermes_ip = ""
self.hermes_code_version = -1
self.hermes_board_id = -1
self.is_HermesLite2 = False
self.hermes_temperature = 0.0
self.hermes_fwd_power = 0.0
self.hermes_rev_power = 0.0
@@ -166,7 +170,10 @@ class Hardware(BaseHardware):
self.vna_count = 0
self.vna_started = False
self.repeater_freq = None # original repeater output frequency
self.antenna_labels = ('Ant 0', 'Ant 1') # labels for the antenna button
if conf.hardware_file_type == "Red Pitaya":
self.antenna_labels = ('Ant 0', 'Ant 1') # labels for the antenna button
else:
self.antenna_labels = ('Ant 0', 'Ant 1', 'Ant 2', 'Ant 3')
self.antenna_index = 0 # index of antenna in use
self.delay_config = True # Delay sending message to HL2 until after sound starts
self.TFRC_counter = 0 # Call for power etc. at intervals
@@ -271,6 +278,7 @@ class Hardware(BaseHardware):
self.hermes_ip = addr[0]
self.hermes_code_version = data[9]
self.hermes_board_id = data[10]
self.is_HermesLite2 = self.hermes_board_id == 6 and self.conf.hardware_file_type == "Hermes"
QS.set_hermes_id(data[9], data[10])
if data[0x16] >> 6 == 0:
QS.set_params(bandscopeScale = 2048)
@@ -317,6 +325,7 @@ class Hardware(BaseHardware):
bid = 6
self.hermes_code_version = code
self.hermes_board_id = bid
self.is_HermesLite2 = self.hermes_board_id == 6 and self.conf.hardware_file_type == "Hermes"
QS.set_hermes_id(code, bid)
st = 'Capture from Hermes device at specified IP %s' % self.hermes_ip
found = True
@@ -331,9 +340,13 @@ class Hardware(BaseHardware):
def open(self):
self.delay_config = True # Delay sending message to HL2 until after sound starts
# This list only changes control bits; no use of WriteQueue()
for name in ('keyupDelay',
'hermes_lowpwr_tr_enable', 'hermes_PWM', 'hermes_disable_sync', 'hermes_power_amp', 'Hermes_BandDictEnTx'):
self.ImmediateChange(name)
self.ImmediateChange('keyupDelay')
self.ImmediateChange('Hermes_BandDictEnTx')
if self.is_HermesLite2:
for name in ('hermes_lowpwr_tr_enable', 'hermes_PWM', 'hermes_disable_sync', 'hermes_power_amp'):
self.ImmediateChange(name)
#else: # DEBUG: Turn on power amp for testing OpenHPSDR radio with my Hermes Lite2
# self.SetControlBit(0x09, 19, 1)
return self.config_text
def GetValue(self, name): # return values stored in the hardware
if name == 'Hware_Hl2_EepromIP':
@@ -492,7 +505,15 @@ class Hardware(BaseHardware):
self.key_was_down = key_down
if self.TFRC_counter >= 3:
self.TFRC_counter = 0
self.hermes_temperature, self.hermes_fwd_power, self.hermes_rev_power, self.hermes_pa_current, self.hermes_fwd_peak, self.hermes_rev_peak = QS.get_hermes_TFRC()
AIN1_avg, AIN1_peak, AIN2_avg, AIN2_peak, AIN3, AIN4, AIN5, AIN6 = QS.get_hermes_TFRC()
if self.hermes_board_id == 6:
(self.hermes_fwd_power, self.hermes_fwd_peak, self.hermes_rev_power, self.hermes_rev_peak,
self.hermes_pa_current, self.hermes_temperature) = AIN1_avg, AIN1_peak, AIN2_avg, AIN2_peak, AIN3, AIN5
else:
(self.hermes_fwd_power, self.hermes_fwd_peak, self.hermes_rev_power, self.hermes_rev_peak,
self.hermes_pa_current, self.hermes_temperature) = AIN1_avg, AIN1_peak, AIN2_avg, AIN2_peak, AIN3, AIN5
if DEBUG_SENSORS:
print("AIN 1-6 %4d %4d %4d %4d %4d %4d" % (AIN1_avg, AIN2_avg, AIN3, AIN4, AIN5, AIN6))
if self.application.bottom_widgets:
self.application.bottom_widgets.UpdateText()
if self.delay_config and QS.get_params('rx_udp_started'):
@@ -529,7 +550,9 @@ class Hardware(BaseHardware):
def ChangeBandFilters(self):
if not hasattr(self.application, "multi_rx_screen"):
return # Needed for the VNA program
self.SetControlBit(0x00, 13, self.antenna_index)
self.SetControlBit(0x00, 13, self.antenna_index & 0x01)
self.SetControlBit(0x00, 14, self.antenna_index & 0x02)
self.SetControlBit(0x00, 15, self.antenna_index > 0)
highest = self.band
freq = self.conf.BandEdge.get(highest, (0, 0))[0]
for pane in self.application.multi_rx_screen.receiver_list:
@@ -606,12 +629,7 @@ class Hardware(BaseHardware):
return (48000, 384000)
## Hardware AGC is no longer supported in HL2 identifying as version >=40
def ChangeAGC(self, value):
if value:
self.pc2hermes[2] |= 0x10 # C0 index == 0, C3[4]: AGC enable
else:
self.pc2hermes[2] &= ~0x10
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change AGC to", value)
pass
## Simpler LNA setting for HL2 identifying as version >=40, see HL2 wiki for details
def ChangeLNA(self, value): # LNA for Rx
# value is -12 to +48
@@ -624,7 +642,7 @@ class Hardware(BaseHardware):
value = 51 - value
else:
value = ((value+12) & 0x3f) | 0x40
self.pc2hermes[4 * 10 + 3] = value # C0 index == 0x1010, C4[4:0] LNA 0-32 dB gain
self.pc2hermes[4 * 10 + 3] = value # C0 index == 0b1010, C4[4:0] LNA 0-32 dB gain
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change LNA to", value)
def ChangeTxLNA(self, value): # LNA for Tx
@@ -634,9 +652,17 @@ class Hardware(BaseHardware):
elif value > 48:
value = 48
value = ((value+12) & 0x3f) | 0x40 | 0x80
self.SetControlByte(0x0e, 3, value, False) # C0 index == 0x0e, C3
self.SetControlByte(0x0e, 3, value, False) # C0 index == 0b1110 == 0x0e, C3
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change Tx LNA to", value)
def ChangeAtten(self, value):
# value is 0 to 31
self.pc2hermes[4 * 10 + 3] = value & 0x1F # C0 index == 0b1010, C4[4:0] attenuator, 0 to 31 dB
self.SetControlBit(0b1010, 5, value) # enable attenuator
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change attenuator to", value)
def ChangePreamp(self, value):
self.SetControlBit(0b0000, 10, value) # enable preamp
def SetTxLevel(self):
try:
tx_level = self.conf.tx_level[self.band]
@@ -653,7 +679,7 @@ class Hardware(BaseHardware):
tx_level = 0
elif tx_level > 255:
tx_level = 255
self.pc2hermes[4 * 9] = tx_level # C0 index == 0x1001, C1[7:0] Tx level
self.pc2hermes[4 * 9] = tx_level # C0 index == 0b1001, C1[7:0] Tx level
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print("Change tx_level to", tx_level)
def MultiRxCount(self, count): # count == number of additional receivers besides the Tx/Rx receiver: 1, 2, 3
@@ -876,6 +902,8 @@ class Hardware(BaseHardware):
QS.set_hermeslite_writepointer(0)
return False
def WriteQueue(self, wait=False):
if not self.is_HermesLite2:
return False
self._wait_queue()
# Send next write
self.old_writequeue = self.pc2hermeslitewritequeue[:]
+6
View File
@@ -154,6 +154,12 @@ class BottomWidgets: # Add extra widgets to the bottom of the screen
current = " PA %4.0f ma" % (1000*current)
self.text_pa_current.SetLabel(current)
# forward and reverse peak power
if not self.hardware.power_interpolator: # For no interpolator, show ADC
fwd = self.hardware.hermes_fwd_power
rev = self.hardware.hermes_rev_power
self.text_fwd_power.SetLabel("Fwd ADC %4d" % fwd)
self.text_swr.SetLabel("Rev ADC %4d" % rev)
return
fwd, rev = self.Code2FwdRevWatts(self.hardware.hermes_fwd_peak, self.hardware.hermes_rev_peak)
# forward less reverse power
power = fwd - rev
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
View File
@@ -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
+3 -9
View File
@@ -1,8 +1,5 @@
# This is the hardware control file for my shack.
# It is for the Hermes-Lite2 5 watt output which uses only the antenna tuner.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
# It is for the Hermes-Lite2 RF1 output and Alt Rx input.
from hermes.quisk_hardware import Hardware as BaseHw
from n2adr import station_hardware
@@ -17,7 +14,7 @@ class Hardware(BaseHw):
self.controlbox = station_hardware.ControlBox(app, conf) # Control my Station Control Box
self.v2filter = station_hardware.FilterBoxV2(app, conf) # Control V2 filter box
def open(self):
if False:
if False: # Start the station control GUI
from n2adr.station_hardware import StationControlGUI
self.GUI = StationControlGUI(self.application.main_frame, self, self.application, self.conf)
self.GUI.Show()
@@ -29,12 +26,11 @@ class Hardware(BaseHw):
return BaseHw.close(self)
def ChangeFilterFrequency(self, tx_freq):
if tx_freq and tx_freq > 0:
self.v2filter.SetTxFreq(tx_freq)
if self.GUI:
self.GUI.SetTxFreq(tx_freq)
self.GUI.freq_entry.ChangeValue("%.3f" % (tx_freq * 1E-6))
else:
self.anttuner.SetTxFreq(tx_freq)
self.v2filter.SetTxFreq(tx_freq)
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
self.ChangeFilterFrequency(tx_freq)
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
@@ -42,8 +38,6 @@ class Hardware(BaseHw):
# band is a string: "60", "40", "WWV", etc.
ret = BaseHw.ChangeBand(self, band)
self.anttuner.ChangeBand(band)
#self.lpfilter.ChangeBand(band)
#self.hpfilter.ChangeBand(band)
self.v2filter.ChangeBand(band)
self.CorrectSmeter()
return ret
+4 -50
View File
@@ -1,56 +1,10 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import wx, time
import _quisk as QS
class BottomWidgets: # Add extra widgets to the bottom of the screen
from hermes.quisk_widgets import BottomWidgets as BaseWidgets
class BottomWidgets(BaseWidgets): # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
#self.config = conf
#self.hardware = hardware
#self.application = app
#start_row = app.widget_row # The first available row
#start_col = app.button_start_col # The start of the button columns
#b = app.QuiskCycleCheckbutton(frame, self.OnAntTuner, ('Antenna', 'Ant 0', 'Ant 1'))
#bw, bh = b.GetMinSize()
#gbs.Add(b, (start_row, start_col), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L+')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 2), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L-')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 4), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C+')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 6), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C-')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 8), (1, 2), flag=wx.EXPAND)
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'Save')
#b.Enable(0)
#gbs.Add(b, (start_row, start_col + 10), (1, 2), flag=wx.EXPAND)
#self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
#gbs.Add(self.swr_label, (start_row, start_col + 2), (1, 10), flag=wx.EXPAND)
#b = app.QuiskCheckbutton(frame, None, text='')
#gbs.Add(b, (start_row, start_col + 25), (1, 2), flag=wx.EXPAND)
# Example of a horizontal slider:
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
# def OnPreamp(self, event):
# print event.GetPosition()
self.num_rows_added = 0
#def OnAntTuner(self, event):
# btn = event.GetEventObject()
# text = btn.GetLabel()
# self.hardware.OnAntTuner(text)
#def UpdateText(self, text):
# self.swr_label.SetLabel(text)
BaseWidgets.__init__(self, app, hardware, conf, frame, gbs, vertBox)
+85 -123
View File
@@ -1,9 +1,5 @@
# This file supports various hardware boxes at my shack
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, struct, math, socket, select, time, traceback, os
import wx, wx.lib.buttons
@@ -411,18 +407,15 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
self.set_HighZ = 0
self.antnum = 0 # Antenna number 0 or 1
self.dipole2 = AntennaControl(app, conf) # Control the KI8BV dipole
if False and conf.use_rx_udp == 10: # Hermes UDP protocol for Hermes-Lite2
path = 'TunerLCZ_HL2.txt'
else:
path = 'TunerLCZ.txt'
path = 'TunerLCZ.txt'
if sys.platform == "win32":
path = 'C:/pub/' + path
self.path = 'C:/pub/' + path
else:
path = '/home/jim/pub/' + path
fp = open(path, "r")
self.path = '/home/jim/pub/' + path
fp = open(self.path, "r")
lines = fp.readlines()
fp.close()
self.TunerLCZ = [(0, 0, 0, 0), (999111000, 0, 0, 0)] # Add dummy first and last entry.
self.TunerLCZ = []
for line in lines:
freq, antL, antC, hilo = line.split()
freq = int(freq)
@@ -451,7 +444,12 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
pass
def ChangeBand(self, band):
pass
def FindLCZ(self, tx_freq):
def SaveLCZ(self):
fp = open(self.path, 'w')
for tup in self.TunerLCZ:
fp.write("%12d %2d %2d %d\n" % tup)
fp.close()
def FindIndexTup(self, tx_freq):
i1 = 0
i2 = len(self.TunerLCZ) - 1
while 1: # binary partition
@@ -462,20 +460,14 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
i2 = i
if i2 - i1 <= 1:
break
# The correct setting is between i1 and i2.
# The correct setting is between i1 and i2, or below i1 or above i2.
# Choose the index that is closest in frequency.
delta1 = tx_freq - self.TunerLCZ[i1][0]
delta2 = self.TunerLCZ[i2][0] - tx_freq
if i1 == 0: # below the first frequency
if abs(tx_freq - self.TunerLCZ[i1][0]) < abs(tx_freq - self.TunerLCZ[i2][0]):
index = i1
elif i2 == len(self.TunerLCZ) - 1: # above the last frequency
else:
index = i2
elif delta2 < delta1: # i2 is closer
index = i2
else: # i1 is closer
index = i1
if DEBUG: print ("AntennaTuner FindLCZ", tx_freq, i1, i2, len(self.TunerLCZ), self.TunerLCZ[index])
return self.TunerLCZ[index]
if DEBUG: print ("StatuinControlGUI FindIndexTup", tx_freq, i1, i2, len(self.TunerLCZ), index, self.TunerLCZ[index])
return index, self.TunerLCZ[index]
def SetTxFreq(self, tx_freq, no_tune=False):
#if DEBUG: print("AntennaTuner SetTxFreq", tx_freq)
self.tx_freq = tx_freq
@@ -484,10 +476,10 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
if tx_freq < 17000000:
if DEBUG and self.antnum == 1: print ("antnum 0")
self.antnum = 0
else:
elif self.dipole2:
if DEBUG and self.antnum == 0: print ("antnum 1")
self.antnum = 1
if self.antnum == 1:
if self.antnum == 1 and self.dipole2:
self.dipole2.SetTxFreq(tx_freq)
if not self.socket:
return
@@ -496,7 +488,7 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
if abs(self.tune_freq - tx_freq) < 5000: # ignore small changes
return
self.tune_freq = tx_freq
f, newL, newC, newH = self.FindLCZ(tx_freq)
index, (f, newL, newC, newH) = self.FindIndexTup(tx_freq)
if newH != self.set_HighZ or self.set_C != newC or self.set_L != newL:
self.set_HighZ = newH
self.set_C = newC
@@ -521,7 +513,8 @@ class AntennaTuner: # Control my homebrew antenna tuner and my KI8BV dipole
self.timer = 0
else:
self.timer = 0
self.dipole2.HeartBeat()
if self.dipole2:
self.dipole2.HeartBeat()
def Send(self):
try:
self.socket.send(self.want_data)
@@ -539,26 +532,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
self.conf = conf
self.data_saving = False
self.tx_freq = 0
if sys.platform == "win32":
self.filename = 'C:/pub/TunerLCZ.tmp'
else:
self.filename = '/home/jim/pub/TunerLCZ.tmp'
try:
fp = open(self.filename, "r")
except FileNotFoundError:
lines = ()
else:
lines = fp.readlines()
fp.close()
self.TunerLCZ = [(0, 0, 0, 0), (999111000, 0, 0, 0)] # Add dummy first and last entry.
for line in lines:
freq, antL, antC, hilo = line.split()
freq = int(freq)
antL = int(antL)
antC = int(antC)
hilo = int(hilo)
self.TunerLCZ.append((freq, antL, antC, hilo))
self.TunerLCZ.sort()
self.anttuner = self.hware.anttuner
self.SetBackgroundColour('light steel blue')
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyDown)
sizer = wx.GridBagSizer(hgap=5, vgap=3)
@@ -568,6 +542,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
self.bands = []
for band in ('160', '80', '60', '40', '30', '20', '17', '15', '12', '10'):
b = wx.lib.buttons.GenToggleButton(self, -1, band)
b.Enable(False)
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
self.bands.append(b)
@@ -604,6 +579,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
szr = wx.BoxSizer(wx.HORIZONTAL)
szr.AddSpacer(20)
b = wx.lib.buttons.GenToggleButton(self, -1, "Key Down")
b.Enable(False)
b.SetFont(font)
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
@@ -611,6 +587,7 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
szr.Add(b)
szr.AddSpacer(10)
b = wx.lib.buttons.GenToggleButton(self, -1, "FilterV2 Preamp")
b.Enable(False)
b.SetFont(font)
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
@@ -622,16 +599,31 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
b.SetFont(font)
szr.Add(b, flag=wx.ALIGN_CENTER_VERTICAL)
szr.AddSpacer(10)
self.freq_entry = wx.TextCtrl(self, value='0000.000', style=wx.TE_PROCESS_ENTER)
self.freq_entry = wx.StaticText(self, -1, "0000.000")
#self.freq_entry = wx.TextCtrl(self, value='0000.000', style=wx.TE_PROCESS_ENTER)
self.freq_entry.SetFont(font)
self.Bind(wx.EVT_TEXT_ENTER, self.OnFreqEntry, self.freq_entry)
szr.Add(self.freq_entry)
#self.Bind(wx.EVT_TEXT_ENTER, self.OnFreqEntry, self.freq_entry)
szr.Add(self.freq_entry, flag=wx.ALIGN_CENTER_VERTICAL)
szr.AddSpacer(10)
b = wx.lib.buttons.GenButton(self, -1, "Search Freq")
b = wx.lib.buttons.GenButton(self, -1, "Freq-")
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
b.SetFont(font)
self.Bind(wx.EVT_BUTTON, self.OnButtonSearch, b)
self.Bind(wx.EVT_BUTTON, self.OnButtonDown, b)
szr.Add(b)
szr.AddSpacer(10)
b = wx.lib.buttons.GenButton(self, -1, "Freq+")
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
b.SetFont(font)
self.Bind(wx.EVT_BUTTON, self.OnButtonUp, b)
szr.Add(b)
szr.AddSpacer(10)
b = wx.lib.buttons.GenButton(self, -1, "Delete")
b.SetUseFocusIndicator(False)
b.SetBezelWidth(4)
b.SetFont(font)
self.Bind(wx.EVT_BUTTON, self.OnButtonDelete, b)
szr.Add(b)
sizer.Add(szr, pos=(row, 0), span=(1, 3))
if frame is None:
@@ -647,46 +639,22 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
self.Bind(wx.EVT_TIMER, self.HeartBeat)
self.timer.Start(100)
def SetTxFreq(self, tx_freq):
#print("StationControlGUI SetTxFreq", tx_freq)
tx_freq = ((tx_freq + 500) // 1000) * 1000
self.tx_freq = tx_freq
index, tup = self.FindIndexTup(tx_freq)
anttuner = self.anttuner
index, tup = anttuner.FindIndexTup(tx_freq)
self.sliderL.SetValue(tup[1])
self.sliderC.SetValue(tup[2])
self.btnHiLo.SetValue(tup[3])
anttuner = self.hware.anttuner
fff, anttuner.set_L, anttuner.set_C, anttuner.set_HighZ = tup
if tx_freq < 17000000:
anttuner.antnum = 0
else:
elif anttuner.dipole2:
anttuner.antnum = 1
if anttuner.antnum == 1:
if anttuner.antnum == 1 and anttuner.dipole2:
anttuner.dipole2.SetTxFreq(tx_freq)
anttuner.WantData()
def FindIndexTup(self, tx_freq):
i1 = 0
i2 = len(self.TunerLCZ) - 1
while 1: # binary partition
i = (i1 + i2) // 2
if self.TunerLCZ[i][0] < tx_freq:
i1 = i
else:
i2 = i
if i2 - i1 <= 1:
break
# The correct setting is between i1 and i2.
# Choose the index that is closest in frequency.
delta1 = tx_freq - self.TunerLCZ[i1][0]
delta2 = self.TunerLCZ[i2][0] - tx_freq
if i1 == 0: # below the first frequency
index = i1
elif i2 == len(self.TunerLCZ) - 1: # above the last frequency
index = i2
elif delta2 < delta1: # i2 is closer
index = i2
else: # i1 is closer
index = i1
if DEBUG: print ("StatuinControlGUI FindLCZ", tx_freq, i1, i2, len(self.TunerLCZ), index, self.TunerLCZ[index])
return index, self.TunerLCZ[index]
self.freq_entry.SetLabel("%.3f" % (tx_freq * 1E-6))
def OnBtnClose(self, event):
self.hware.anttuner.close()
self.hware.controlbox.close()
@@ -730,16 +698,15 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
f = ((f + 500) // 1000) * 1000
if self.hware.v2filter:
self.hware.v2filter.SetTxFreq(f)
self.hware.anttuner.SetTxFreq(f, no_tune=True)
self.anttuner.SetTxFreq(f, no_tune=True)
self.freq_entry.ChangeValue("%.3f" % (f * 1E-6))
self.SetTxFreq(f)
def OnButtonHiLo(self, event):
anttuner = self.hware.anttuner
if self.btnHiLo.GetValue():
anttuner.set_HighZ = 1
self.anttuner.set_HighZ = 1
else:
anttuner.set_HighZ = 0
anttuner.WantData()
self.anttuner.set_HighZ = 0
self.anttuner.WantData()
def OnFreqEntry(self, event=None):
freq = self.freq_entry.GetValue()
freq = float(freq) * 1E6
@@ -751,11 +718,27 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
freq = int(freq + 0.1)
self.tx_freq = freq
self.SetTxFreq(self.tx_freq)
def OnButtonDown(self, event):
index, tup = self.anttuner.FindIndexTup(self.tx_freq)
index -= 1
if index >= 0:
self.SetTxFreq(self.anttuner.TunerLCZ[index][0])
def OnButtonUp(self, event):
index, tup = self.anttuner.FindIndexTup(self.tx_freq)
index += 1
if index < len(self.anttuner.TunerLCZ):
self.SetTxFreq(self.anttuner.TunerLCZ[index][0])
def OnButtonDelete(self, event):
index, tup = self.anttuner.FindIndexTup(self.tx_freq)
if self.tx_freq == self.anttuner.TunerLCZ[index][0]:
del self.anttuner.TunerLCZ[index]
if index > 0:
self.SetTxFreq(self.anttuner.TunerLCZ[index - 1][0])
elif len(self.anttuner.TunerLCZ) > 0:
self.SetTxFreq(self.anttuner.TunerLCZ[0][0])
else:
self.SetTxFreq(0)
def OnButtonSave(self, event):
freq = self.freq_entry.GetValue()
freq = float(freq) * 1E6
freq = int(freq + 0.1)
self.tx_freq = freq
L = self.sliderL.GetValue()
C = self.sliderC.GetValue()
if self.btnHiLo.GetValue():
@@ -763,22 +746,19 @@ class StationControlGUI(wx.Frame): # Display a stand-alone control window for
else:
hilo = 0
tup = (self.tx_freq, L, C, hilo)
index, t = self.FindIndexTup(self.tx_freq)
if self.tx_freq == self.TunerLCZ[index][0]:
self.TunerLCZ[index] = tup
index, t = self.anttuner.FindIndexTup(self.tx_freq)
if self.tx_freq == self.anttuner.TunerLCZ[index][0]:
self.anttuner.TunerLCZ[index] = tup
else:
self.TunerLCZ.append(tup)
self.TunerLCZ.sort()
fp = open(self.filename, 'w')
for tup in self.TunerLCZ[1:-1]: # Throw away dummy entries
fp.write("%12d %2d %2d %d\n" % tup)
fp.close()
self.anttuner.TunerLCZ.append(tup)
self.anttuner.TunerLCZ.sort()
self.anttuner.SaveLCZ()
def OnSliderL(self, event=None):
self.hware.anttuner.set_L = self.sliderL.GetValue()
self.hware.anttuner.WantData()
self.anttuner.set_L = self.sliderL.GetValue()
self.anttuner.WantData()
def OnSliderC(self, event=None):
self.hware.anttuner.set_C = self.sliderC.GetValue()
self.hware.anttuner.WantData()
self.anttuner.set_C = self.sliderC.GetValue()
self.anttuner.WantData()
def OnBtnV2Preamp(self, event):
if self.hware.v2filter:
self.hware.v2filter.SetPreamp(event.GetEventObject().GetValue())
@@ -1072,21 +1052,3 @@ class AT200PC: # Control an AT-200PC autotuner made by LDG
self.set_C += 1
elif text == 'C-':
self.set_C -= 1
class App(wx.App):
def OnInit(self):
if sys.path[0] != "'.'": # Make sure the current working directory is on path
sys.path.insert(0, '.')
import quisk_conf_defaults as conf
import quisk_hardware_model as hardware
self.bottom_widgets = None
hardware.anttuner = AntennaTuner(self, conf) # Control the antenna tuner
hardware.v2filter = FilterBoxV2(self, conf) # Control V2 filter box
hardware.controlbox = ControlBox(self, conf) # Control my Station Control Box
self.main_frame = frame = StationControlGUI (None, hardware, self, conf)
frame.Open()
frame.Show()
return True
if __name__ == '__main__':
App().MainLoop()
+1 -1
View File
@@ -18,7 +18,7 @@ from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 1
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
################ Receivers PerseusSDR, The PerseusSDR interface to multiple hardware SDRs.
################ Receivers PerseusSDR, The PerseusSDR interface to multiple SDRs.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'perseuspkg/quisk_hardware.py'
+59 -37
View File
@@ -218,14 +218,17 @@ static unsigned char quisk_hermes_to_pc[5 * 4]; // data received from the Herme
static unsigned char quisk_hermeslite_response[5]; // response from Hermes-Lite commands
unsigned int quisk_hermes_code_version = -1; // code version returned by the Hermes hardware
unsigned int quisk_hermes_board_id = -1; // board ID returned by the Hermes hardware
static double hermes_temperature; // average temperature
static double hermes_fwd_power; // average forward power
static double hermes_rev_power; // average reverse power
static double hermes_fwd_peak; // peak forward power
static double hermes_rev_peak; // peak reverse power
static double hermes_pa_current; // average power amp current
static int hermes_count_temperature; // number of temperature samples
static int hermes_count_current; // number of current samples
static double hpsdr_AIN1_avg; // average forward power
static double hpsdr_AIN1_peak; // peak forward power
static double hpsdr_AIN2_avg; // average reverse power
static double hpsdr_AIN2_peak; // peak reverse power
static double hpsdr_AIN3; // average power amp current
static double hpsdr_AIN4;
static double hpsdr_AIN5; // average temperature
static double hpsdr_AIN6;
static int hpsdr_AIN3_count; // number of current samples
static int hpsdr_AIN4_count; // number of current samples
static int hpsdr_AIN5_count; // number of current samples
static int hardware_ptt; // hardware PTT switch
int quisk_hardware_cwkey; // hardware CW key from UDP or USB
@@ -3136,31 +3139,43 @@ static PyObject * get_hermes_TFRC(PyObject * self, PyObject * args)
if (!PyArg_ParseTuple (args, ""))
return NULL;
if (hermes_count_temperature > 0) {
hermes_temperature /= hermes_count_temperature;
hermes_fwd_power /= hermes_count_temperature;
if (hpsdr_AIN5_count > 0) {
hpsdr_AIN5 /= hpsdr_AIN5_count;
hpsdr_AIN1_avg /= hpsdr_AIN5_count;
}
else {
hermes_temperature = 0.0;
hermes_fwd_power = 0.0;
hpsdr_AIN5 = 0.0;
hpsdr_AIN1_avg = 0.0;
}
if (hermes_count_current > 0) {
hermes_rev_power /= hermes_count_current;
hermes_pa_current /= hermes_count_current;
if (hpsdr_AIN3_count > 0) {
hpsdr_AIN2_avg /= hpsdr_AIN3_count;
hpsdr_AIN3 /= hpsdr_AIN3_count;
}
else {
hermes_rev_power = 0.0;
hermes_pa_current = 0.0;
hpsdr_AIN2_avg = 0.0;
hpsdr_AIN3 = 0.0;
}
ret = Py_BuildValue("dddddd", hermes_temperature, hermes_fwd_power, hermes_rev_power, hermes_pa_current, hermes_fwd_peak, hermes_rev_peak);
hermes_temperature = 0;
hermes_fwd_power = 0;
hermes_rev_power = 0;
hermes_fwd_peak = 0;
hermes_rev_peak = 0;
hermes_pa_current = 0;
hermes_count_temperature = 0;
hermes_count_current = 0;
if (hpsdr_AIN4_count > 0) {
hpsdr_AIN4 /= hpsdr_AIN4_count;
hpsdr_AIN6 /= hpsdr_AIN4_count;
}
else {
hpsdr_AIN4 = 0.0;
hpsdr_AIN6 = 0.0;
}
ret = Py_BuildValue("dddddddd", hpsdr_AIN1_avg, hpsdr_AIN1_peak, hpsdr_AIN2_avg, hpsdr_AIN2_peak,
hpsdr_AIN3, hpsdr_AIN4, hpsdr_AIN5, hpsdr_AIN6);
hpsdr_AIN1_avg = 0;
hpsdr_AIN1_peak = 0;
hpsdr_AIN2_avg = 0;
hpsdr_AIN2_peak = 0;
hpsdr_AIN3 = 0;
hpsdr_AIN4 = 0;
hpsdr_AIN5 = 0;
hpsdr_AIN6 = 0;
hpsdr_AIN3_count = 0;
hpsdr_AIN4_count = 0;
hpsdr_AIN5_count = 0;
return ret;
}
@@ -3708,19 +3723,24 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
quisk_set_play_state();
}
}
else if(dindex == 1) { // temperature and forward power
hermes_temperature += quisk_hermes_to_pc[4] << 8 | quisk_hermes_to_pc[5];
else if(dindex == 1) {
hpsdr_AIN5 += quisk_hermes_to_pc[4] << 8 | quisk_hermes_to_pc[5];
power = quisk_hermes_to_pc[6] << 8 | quisk_hermes_to_pc[7];
hermes_fwd_power += power;
hermes_fwd_peak = fmax(hermes_fwd_peak, (double)power);
hermes_count_temperature++;
hpsdr_AIN1_avg += power;
hpsdr_AIN1_peak = fmax(hpsdr_AIN1_peak, (double)power);
hpsdr_AIN5_count++;
}
else if (dindex == 2) { // reverse power and current
else if (dindex == 2) {
power = quisk_hermes_to_pc[8] << 8 | quisk_hermes_to_pc[9];
hermes_rev_power += power;
hermes_rev_peak = fmax(hermes_rev_peak, (double)power);
hermes_pa_current += quisk_hermes_to_pc[10] << 8 | quisk_hermes_to_pc[11];
hermes_count_current++;
hpsdr_AIN2_avg += power;
hpsdr_AIN2_peak = fmax(hpsdr_AIN2_peak, (double)power);
hpsdr_AIN3 += quisk_hermes_to_pc[10] << 8 | quisk_hermes_to_pc[11];
hpsdr_AIN3_count++;
}
else if (dindex == 3) {
hpsdr_AIN4 += quisk_hermes_to_pc[12] << 8 | quisk_hermes_to_pc[13];
hpsdr_AIN6 += quisk_hermes_to_pc[14] << 8 | quisk_hermes_to_pc[15];
hpsdr_AIN4_count++;
}
// convert 24-bit samples to 32-bit samples; int must be 32 bits.
index = start + 5;
@@ -6168,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."},
+8
View File
@@ -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 *);
+56 -19
View File
@@ -995,17 +995,12 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
else:
self.Send('0\n')
def GetFreq(self): # The Rx frequency
rx = self.app.multi_rx_screen.receiver_list
if rx:
rx = rx[0]
self.Reply('Frequency', rx.txFreq + rx.VFO, 0)
if HAMLIB_DEBUG:
print ("GetFreq rx", rx.txFreq, rx.VFO)
else:
self.Reply('Frequency', self.app.rxFreq + self.app.VFO, 0)
if HAMLIB_DEBUG:
print ("GetFreq app", self.app.rxFreq, self.app.txFreq, self.app.VFO)
# This is always the main Rx frequency without reference to added receivers.
self.Reply('Frequency', self.app.rxFreq + self.app.VFO, 0)
if HAMLIB_DEBUG:
print ("GetFreq app", self.app.rxFreq, self.app.txFreq, self.app.VFO)
def SetFreq(self): # The Rx frequency
# This is always the main Rx frequency without reference to added receivers.
freq = self.GetParamNumber()
try:
freq = float(freq)
@@ -1014,12 +1009,7 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
self.ErrParam()
else:
freq = int(freq + 0.5)
# For multiple receivers this controls the first added receiver frequency; else it controls split Rx/Tx frequency.
rx = self.app.multi_rx_screen.receiver_list
if rx:
rx[0].ChangeRxTxFrequency(freq)
else:
self.app.ChangeRxTxFrequency(freq, None)
self.app.ChangeRxTxFrequency(freq, None)
def GetSplitFreq(self): # The Tx Frequency
self.Reply('TX Frequency', self.app.txFreq + self.app.VFO, 0)
def SetSplitFreq(self): # The Tx Frequency
@@ -3728,7 +3718,7 @@ class App(wx.App):
'wfallScaleZ', 'wfallGrScaleZ', 'graphScaleZ', 'split_rxtx_play', 'modeFilter',
'file_name_rec_audio', 'file_name_rec_samples', 'file_name_rec_mic', 'file_name_rec_tmp',
'file_name_play_audio', 'file_name_play_samples', 'file_name_play_cq',
'file_play_source', 'hermes_LNA_dB']
'file_play_source', 'hermes_LNA_dB', 'hermes_atten_dB']
def __init__(self):
global application
QS.AppStatus(1)
@@ -3948,7 +3938,9 @@ 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"):
self.want_RxTx = True
else:
@@ -4037,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)
@@ -4284,6 +4278,10 @@ class App(wx.App):
self.main_frame.SetClientSize(wx.Size(width, self.height))
if hasattr(Hardware, 'pre_open'): # pre_open() is called before open()
Hardware.pre_open()
try:
self.is_HermesLite2 = Hardware.is_HermesLite2 # This is set in pre_open()
except:
self.is_HermesLite2 = False
if self.local_conf.GetWidgets(self, Hardware, conf, frame, gbs, vertBox):
pass
elif conf.quisk_widgets:
@@ -4358,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()
@@ -4767,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:
@@ -5770,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"):
@@ -6155,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':
@@ -6682,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 ":
@@ -6741,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:
@@ -6808,7 +6832,20 @@ 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 conf.hermes_lite2_enable:
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:
self.tx_inhibit = 0
+149 -22
View File
@@ -48,7 +48,7 @@ quisk_widgets = None
################ Receivers SoftRock USB, Devices controlled by USB that capture samples from a sound card, and (for Tx) play samples to a sound card
################ Receivers SoftRock USB, Sound card radios controlled by USB.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'softrock/hardware_usb.py'
@@ -133,7 +133,7 @@ digital_tx_level = 100
################ Receivers SoftRock Fixed, Fixed frequency devices that capture samples from a sound card, and (for Tx) play samples to a sound card
################ Receivers SoftRock Fixed, Fixed frequency sound card radios.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'quisk_hardware_fixed.py'
@@ -174,7 +174,7 @@ digital_tx_level = 100
################ Receivers HiQSDR, The original N2ADR hardware and the improved HiQSDR using UDP
################ Receivers HiQSDR, The original N2ADR and HiQSDR radios.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hiqsdr/quisk_hardware.py'
@@ -258,7 +258,7 @@ sndp_active = True
################ Receivers Hermes, The Hermes-Lite Project and possibly other hardware with the Hermes FPGA code.
################ Receivers Hermes, The Hermes-Lite project by Steve Haynal.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
@@ -275,7 +275,7 @@ sndp_active = True
# You can set these options:
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for the Hermes protocol.
# This is the type of UDP hardware. Use 10 for the Hermes Lite2 protocol.
#use_rx_udp = 10
## udp_rx_ip Hermes known IP, text
@@ -373,6 +373,7 @@ hermes_disable_sync = False
# be sure to write it down. To use this address you must set "Eeprom IP Usage".
# And you may want to set "Hermes known IP" too. Make sure the address does not conflict
# with your DHCP server.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromIP = '192.168.1.6'
#Hware_Hl2_EepromIP = '192.168.1.241'
@@ -380,6 +381,7 @@ Hware_Hl2_EepromIP = '192.168.1.6'
# This is the way the EEPROM IP address is used at power on.
# "Ignore" means it is not used at all. "Set address" means DHCP is not used and the EEPROM IP address is used.
# "Use DHCP first" means the EEPROM IP address is only used if DHCP fails.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromIPUse = 'Ignore'
#Hware_Hl2_EepromIPUse = 'Use DHCP first'
#Hware_Hl2_EepromIPUse = 'Set address'
@@ -390,6 +392,7 @@ Hware_Hl2_EepromIPUse = 'Ignore'
# at power on instead of the factory supplied MAC address 0:1c:c0:a2:13:dd. It is only used at power on,
# and it is only used if it is marked valid by setting "Eeprom MAC usage" to "Set address".
# If you have two HL2's, you must have a unique MAC address for each.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromMAC = '0xA1 0x6B'
#Hware_Hl2_EepromMAC = '0x4C 0x33'
@@ -398,6 +401,7 @@ Hware_Hl2_EepromMAC = '0xA1 0x6B'
# the HL2 will use the last two MAC bytes from "Eeprom MAC address"; otherwise the default MAC is used.
# If you change this item, the bit is written to the HL2.
# The HL2 only uses these bits at power on.
# Power cycle the HL2 after changing this item.
Hware_Hl2_EepromMACUse = 'Ignore'
#Hware_Hl2_EepromMACUse = 'Set address'
@@ -474,13 +478,6 @@ hermes_iob_rxin = 'J10 available'
#hermes_iob_rxin = 'HL2 Rx to J9'
#hermes_iob_rxin = 'Use J9 and J10'
## hermes_lite2_enable Hermes Lite2 protocol, boolean
# This turns on some of the protocol extensions for the Hermes Lite2 that are not part
# of the official Hermes Protocol 1. Currently this only enables Tx Inhibit (CN8).
# Changes are immediate (no need to restart).
hermes_lite2_enable = True
#hermes_lite2_enable = False
# These are known power meter calibration tables. This table is not present in the JSON settings file.
power_meter_std_calibrations = {}
@@ -491,6 +488,7 @@ power_meter_std_calibrations['HL2FilterE3'] = [[ 0, 0.0 ], [ 25.865384615384617,
[ 4093.1785714285716, 7.28994845808807 ], [ 4502.496428571429, 8.820837634286566 ], [ 4952.746071428572, 10.673213537486745 ] ]
#power_meter_std_calibrations['HL2FilterE1'] = [[0, 0.0], [9.07, 0.002], [54.98, 0.014], [148.6, 0.057],
# [328.0, 0.208], [611.1, 0.646], [807.0, 1.098], [982.1, 1.6], [1223.3, 2.471], [1517.7, 3.738], [1758.7, 5.02]]
power_meter_std_calibrations['Show ADC'] = []
## Hermes_BandDict Rx IO Bus, dict
# The Hermes_BandDict sets the 7 bits on the J16 connector for Rx.
@@ -508,17 +506,17 @@ Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
################ Receivers Red Pitaya, The Red Pitaya Project by Pavel Demin. This uses the Hermes FPGA code.
################ Receivers Red Pitaya, The Red Pitaya project by Pavel Demin.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = ''
#widgets_file_name = 'hermes/hpsdr_widgets.py'
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for the Hermes protocol.
# This is the type of UDP hardware. Use 10 for the Red Pitaya using the OpenHPSDR protocol.
#use_rx_udp = 10
## rx_udp_ip IP change, text
@@ -586,7 +584,123 @@ Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
################ Receivers SoapySDR, The SoapySDR interface to multiple hardware SDRs.
################ Receivers OpenHPSDR, Radios using the HPSDR Protocol 1.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = 'hermes/hpsdr_widgets.py'
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for hardware using the OpenHPSDR protocol 1.
#use_rx_udp = 10
## rx_udp_ip IP change, text
# This item should be left blank. It is used to change the IP address of the hardware to a different
# IP once the hardware is found. Not all Hermes firmware supports changing the IP address.
#rx_udp_ip = ""
## rx_udp_port Hardware UDP port, integer
# This is the UDP port number of your hardware.
#rx_udp_port = 1024
## rx_udp_ip_netmask Network netmask, text
# This is the netmask for the network.
#rx_udp_ip_netmask = '255.255.255.0'
## tx_ip Transmit IP, text
# Leave this blank to use the same IP address as the receive hardware. Otherwise, enter "disable"
# to disable sending transmit I/Q samples, or enter the actual IP address. You must enter "disable"
# if you have multiple hardwares on the network, and only one should transmit. This item is normally blank.
tx_ip = ""
#tx_ip = "disable"
## tx_audio_port Tx audio UDP port, integer
# This is the UDP port for transmit audio I/Q samples. Enter zero to calculate this from the
# base hardware port. Otherwise enter the special custom port.
tx_audio_port = 0
## rx_udp_clock Clock frequency Hertz, integer
# This is the clock frequency of the hardware in Hertz.
#rx_udp_clock = 122880000
#rx_udp_clock = 76800000
#rx_udp_clock = 125000000
## tx_level Tx Level, dict
# This sets the transmit level 0 to 255 for each band.
# The config screen has a slider 0 to 100% so you can reduce the transmit power.
# The hardware only supports a limited adjustment range, so zero is still a small amount of power.
tx_level = {}
## digital_tx_level Digital Tx power %, integer
# Digital modes reduce power by the percentage on the config screen.
# This is the maximum value of the slider.
#digital_tx_level = 100
## hermes_code_version Hermes code version, integer
# There can be multiple Hermes devices on a network, but Quisk can only use one of these. If you have multiple
# hermes devices, you can use this to specify a unique device. Or use -1 to accept any board.
hermes_code_version = -1
## hermes_board_id Hermes board ID, integer
# There can be multiple Hermes devices on a network, but Quisk can only use one of these. If you have multiple
# hermes devices, you can use this to specify a unique device. Or use -1 to accept any board.
hermes_board_id = -1
## Hermes_BandDict Hermes Bus, dict
# The Hermes_BandDict sets the 7 bits on the J16 connector.
Hermes_BandDict = {
'160':0b0000001, '80':0b0000010, '60':0b0000100, '40':0b0001000, '30':0b0010000, '20':0b0100000, '15':0b1000000}
## Hermes_BandDictTx Tx IO Bus, dict
# The Hermes_BandDictTx sets the 7 bits on the J16 connector for Tx if enabled.
Hermes_BandDictTx = {'160':0, '80':0, '60':0, '40':0, '30':0, '20':0, '17':0, '15':0, '12':0, '10':0}
## Hermes_BandDictEnTx Enable Tx Filt, boolean
# Enable the separate Rx and Tx settings for the J16 connector.
Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
## calibrate_temp_20 PA temp ADC at 20\u2103, integer
# This is the ADC code returned for a temperature of 20C.
# To see the ADC code being returned, enter zero for both temperature codes.
# Linear interpolation is used to calculate the temperature.
# Changes are immediate (no need to restart).
calibrate_temp_20 = 0
## calibrate_temp_40 PA temp ADC at 40\u2103, integer
# This is the ADC code returned for a temperature of 40C.
# To see the ADC code being returned, enter zero for both temperature codes.
# Linear interpolation is used to calculate the temperature.
# Changes are immediate (no need to restart).
calibrate_temp_40 = 0
## calibrate_current_0 PA current ADC at zero, integer
# This is the ADC code returned for a PA current of zero.
# To see the ADC code being returned, enter zero for both current codes.
# Linear interpolation is used to calculate the current.
# Changes are immediate (no need to restart).
calibrate_current_0 = 0
## calibrate_current_1 PA current ADC at 1 amp, integer
# This is the ADC code returned for a PA current of one amp.
# To see the ADC code being returned, enter zero for both current codes.
# Linear interpolation is used to calculate the current.
# Changes are immediate (no need to restart).
calibrate_current_1 = 0
## power_meter_calib_name Power meter calibration, text choice
# This is the calibration table used to convert the power sensor voltage measured by the ADC to the transmit power display.
# It is a table of ADC codes and the corresponding measured power level. If you have a power meter, you can create your own
# table by selecting "New". Then enter ten or more power measurements from low to full power.
# For the Hermes Lite2 version E3 filter board, use the built-in table "HL2FilterE3".
# Changes are immediate (no need to restart).
power_meter_calib_name = 'HL2FilterE3'
################ Receivers SoapySDR, The SoapySDR interface to multiple SDRs.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'soapypkg/quisk_hardware.py'
@@ -602,7 +716,7 @@ Hermes_BandDictEnTx = False
# Further items are present in the radio dictionary with names soapy_*
################ Receivers SdrIQ, The SDR-IQ receiver by RfSpace
################ Receivers SdrIQ, The SDR-IQ receiver by RfSpace.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
hardware_file_name = 'quisk_hardware_sdriq.py'
@@ -633,7 +747,7 @@ sdriq_decimation = 1250
################ Receivers Odyssey, The Odyssey project using a UDP protocol similar to the HiQSDR
################ Receivers Odyssey, The Odyssey project.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hiqsdr/quisk_hardware.py'
@@ -744,7 +858,7 @@ sndp_active = True
#radio_sound_mic_boost = True
################ Receivers Odyssey2, The Odyssey-2 project using the HPSDR Hermes protocol
################ Receivers Odyssey2, The Odyssey-2 project.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'hermes/quisk_hardware.py'
@@ -759,7 +873,7 @@ sndp_active = True
# You can set these options:
## use_rx_udp Hardware type, integer choice
# This is the type of UDP hardware. Use 10 for the Hermes protocol.
# This is the type of UDP hardware. Use 10 for the Odyssey-2.
#use_rx_udp = 10
## rx_udp_ip IP change, text
@@ -828,7 +942,7 @@ Hermes_BandDictEnTx = False
#Hermes_BandDictEnTx = True
################ Receivers Afedri, The Afedri SDR receiver with the Ethernet interface.
################ Receivers Afedri, The Afedri SDR receiver with Ethernet.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'afedrinet/quisk_hardware.py'
@@ -860,7 +974,7 @@ Hermes_BandDictEnTx = False
#default_rf_gain = 11
################ Receivers Control Head, Use Quisk to control a remote radio. No real hardware is used here.
################ Receivers Control Head, Use Quisk to control a remote Quisk radio.
## hardware_file_name Hardware file path, rfile
# Quisk can be used as a control head to control a real radio located remotely.
# This file contains the control logic for remote radios of type Hermes, SoftRock or HiQSDR.
@@ -1309,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
+3 -2
View File
@@ -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.45'
Version = '4.2.52'
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',
+389
View File
@@ -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);
}
+73
View File
@@ -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
+8 -3
View File
@@ -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);
+8 -8
View File
@@ -199,14 +199,14 @@ static int quisk_open_capture(struct sound_dev * dev)
DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)DsEnumCapture, dev);
if (errFound != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device name %s not found", dev->name);
"DirectSound capture device name %.200s not found", dev->name);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", dev->dev_errmsg);
return 1;
}
if (errOpen != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device %s open failed", dev->name);
"DirectSound capture device %.200s open failed", dev->name);
if (quisk_sound_state.verbose_sound)
QuiskPrintf("%s\n", dev->dev_errmsg);
return 1;
@@ -226,7 +226,7 @@ static int quisk_open_capture(struct sound_dev * dev)
}
else {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device %s buffer create failed (0x%lX)", dev->name, hr);
"DirectSound capture device %.200s buffer create failed (0x%lX)", dev->name, hr);
return 1;
}
ptBuf = (LPDIRECTSOUNDCAPTUREBUFFER)dev->buffer;
@@ -235,7 +235,7 @@ static int quisk_open_capture(struct sound_dev * dev)
if (quisk_sound_state.verbose_sound)
QuiskPrintf("Capture start error 0x%lX", hr);
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound capture device %s capture start failed", dev->name);
"DirectSound capture device %.200s capture start failed", dev->name);
return 1;
}
if (quisk_sound_state.verbose_sound) {
@@ -270,18 +270,18 @@ static int quisk_open_playback(struct sound_dev * dev)
DirectSoundEnumerate((LPDSENUMCALLBACK)DsEnumPlay, dev);
if (errFound != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device name %s not found", dev->name);
"DirectSound play device name %.200s not found", dev->name);
return 1;
}
if (errOpen != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device %s open failed", dev->name);
"DirectSound play device %.200s open failed", dev->name);
return 1;
}
hr = IDirectSound_SetCooperativeLevel ((LPDIRECTSOUND8)dev->handle, quisk_mainwin_handle, DSSCL_PRIORITY);
if (hr != DS_OK) {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device %s cooperative level failed", dev->name);
"DirectSound play device %.200s cooperative level failed", dev->name);
return 1;
}
dev->sample_bytes = 4;
@@ -299,7 +299,7 @@ static int quisk_open_playback(struct sound_dev * dev)
}
else {
snprintf (dev->dev_errmsg, QUISK_SC_SIZE,
"DirectSound play device %s buffer create failed (0x%X)", dev->name, (unsigned int)hr);
"DirectSound play device %.180s buffer create failed (0x%X)", dev->name, (unsigned int)hr);
return 1;
}
if (quisk_sound_state.verbose_sound) {
+725
View File
@@ -0,0 +1,725 @@
#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_STREAM_DATA_BYTES 16384
#define TCI_RX_BUF_SIZE 1024
#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 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 and Stream.length is the number of floats (not samples).
// WSJT-X TX_AUDIO_STREAM is twice as long, but the data is in the first half and garbage is in the second half.
// WSJT-X TX_CHRONO Stream.length is the number of floats, and the returned TX_AUDIO_STREAM length is equal to it.
// 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 int64_t quisk_dds;
//static int quisk_if;
static long long quisk_vfo;
static long long 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;
static int tci_started; // Did we start the TCI server?
// Tx Audio
ws_cli_conn_t tci_tx_audio_client; // single client providing Tx audio
double tci_tx_audio_time; // seconds since the start of Tx audio
uint64_t tci_tx_audio_samples; // number of samples requested since the start of Tx audio
int tci_tx_audio_rate; // samples per second
int tci_tx_audio_request; // the Stream.length to request in TX_CHRONO
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
} ;
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[TCI_STREAM_DATA_BYTES]; // 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 = 2,
.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) {
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) {
pthread_mutex_lock(&tx_buffer_mutex);
client_trx = 1;
tci_tx_audio_request = TCI_STREAM_DATA_BYTES /
ctx->audio_stream_bytes_per_sample / ctx->audio_stream_channels;
CircularBuffer(0, NULL, tci_tx_audio_request * 2, 0);
tci_tx_audio_client = client;
tci_tx_audio_time = QuiskTimeSec();
tci_tx_audio_samples = 0;
tci_tx_audio_rate = ctx->audio_stream_samplerate;
pthread_mutex_unlock(&tx_buffer_mutex);
}
}
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 = atoll(arg3);
}
else {
snprintf(char_buf, TCI_COMMAND_SIZE, "vfo:0,0,%lld;", 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;"); Not known due to transverters
//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,%lld;", 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 unsigned char * vpt = msg + header;
const unsigned char * vpt_end = vpt + (size - header);
const unsigned char * vpt2;
const unsigned char * vpt2_end;
int count2;
int buf_start=0, buf_end;
if (verbose >= 3) {
QuiskPrintf("\n\nMSG size %ld length %d\n 0 ", size, pt->length);
vpt2 = msg + header;
vpt2_end = vpt2 + (size - header);
count2 = 0;
while (vpt2 < vpt2_end) {
re = *(float *)vpt2;
vpt2 += sizeof(float);
im = *(float *)vpt2;
vpt2 += sizeof(float);
if (fabsf(re) > 1.1)
re = 9;
if (fabsf(im) > 1.1)
im = 9;
count2++;
QuiskPrintf("%8.4f %8.4f ", re, im);
if (count2 % 5 == 0)
QuiskPrintf("\n%4d ", count2);
}
QuiskPrintf("\n");
}
if (pt->type == TX_AUDIO_STREAM) {
if (pt->length > 0 && client == tci_tx_audio_client) {
pthread_mutex_lock(&tx_buffer_mutex);
if (verbose >= 2)
buf_start = CircularBuffer(0, &sample, 0, 0);
switch (TxChrono.format) {
case TCI_INT16:
break;
case TCI_FLOAT32:
// Version 1.4 does not return the number of channels. We assume two channels.
// pt->length is the number of floats in WSJT-X.
while (vpt < vpt_end && count < pt->length) {
re = *(float *)vpt;
vpt += sizeof(float);
count++;
if (two_channels) {
im = *(float *)vpt;
vpt += sizeof(float);
count++;
}
else {
im = re;
}
sample = (re + I * im) * (CLIP32 / 2);
CircularBuffer(0, &sample, 0, 1);
}
if (verbose && count != pt->length)
QuiskPrintf("TCI *TX count %ld %d %d\n", size, count, pt->length);
break;
}
if (verbose >= 2) {
buf_end = CircularBuffer(0, &sample, 0, 0);
QuiskPrintf("TCI Tx buffer add %4d start %5d end %5d\n", count, buf_start, buf_end);
}
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 + TCI_STREAM_DATA_BYTES;
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]) * (1.0 / 2147483648.0 / 2));
vpt += bytes_per_sample;
stream.length++;
if (two_channels) {
*(float *)vpt = (float)(cimag(cSamples[i]) * (1.0 / 2147483648.0 / 2));
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) {
pthread_mutex_lock(&tx_buffer_mutex); // Copy samples from the Tx buffer
int count = CircularBuffer(0, cSamples, mic_count, 0);
pthread_mutex_unlock(&tx_buffer_mutex);
if (count < mic_count) {
if (verbose >= 2)
QuiskPrintf("TCI *TX replace mic %d %d\n", count, mic_count);
while (count < mic_count)
cSamples[count++] = 0;
}
// Ask for more samples
double dtime = QuiskTimeSec();
if (tci_tx_audio_samples < (dtime - tci_tx_audio_time) * tci_tx_audio_rate) {
TxChrono.length = tci_tx_audio_request;
sendframe_bin(tci_tx_audio_client, (const char *)&TxChrono, 16 * sizeof(uint32_t));
//tci_tx_audio_samples += tci_tx_audio_request;
tci_tx_audio_samples += tci_tx_audio_request / 2; // Stream.length is floats, not samples
if (verbose >= 3)
QuiskPrintf("TCI Request more Tx samples\n");
}
}
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} ;
long long new_vfo=-1;
long long new_dds=-1;
int start=-1, new_if=-1, new_trx=-1, new_split_enable=-1;
char * mode=NULL;
char char_buf[TCI_COMMAND_SIZE];
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiLiLiis", 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,%lld;", 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)
strcpy(quisk_modulation, "lsb");
else if (strcmp(mode, "USB") == 0)
strcpy(quisk_modulation, "usb");
else if (strcmp(mode, "AM") == 0)
strcpy(quisk_modulation, "am");
else if (strcmp(mode, "FM") == 0)
strcpy(quisk_modulation, "fm");
else
strncpy(quisk_modulation, mode, TCI_COMMAND_SIZE - 1);
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 ii;
long long ll;
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) {
ll = client_vfo;
client_vfo = -1;
return PyLong_FromLongLong(ll);
}
else if (strcmp(name, "tci_split_enable") == 0 && client_split_enable >= 0) {
ii = client_split_enable;
client_split_enable = -1;
return PyLong_FromLong(ii);
}
else if (strcmp(name, "tci_trx") == 0 && client_trx >= 0) {
ii = client_trx;
client_trx = -1;
return PyLong_FromLong(ii);
}
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)
strcpy(char_buf, "LSB");
else if (strcmp(client_modulation, "usb") == 0)
strcpy(char_buf, "USB");
else if (strcmp(client_modulation, "am") == 0)
strcpy(char_buf, "AM");
else if (strcmp(client_modulation, "fm") == 0)
strcpy(char_buf, "FM");
client_modulation[0] = 0;
if (char_buf[0])
return PyString_FromString(char_buf);
}
Py_INCREF (Py_None);
return Py_None;
}
+89
View File
@@ -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;
}
+37
View File
@@ -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
+63
View File
@@ -321,3 +321,66 @@ 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 for read/write. If both are zero, return nBuf.
// 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 * 16;
ptChannel->cBuf = (complex double *)realloc(ptChannel->cBuf, ptChannel->sizeBuf * sizeof(complex double));
memset(ptChannel->cBuf, 0, ptChannel->sizeBuf * sizeof(complex double));
ptChannel->nBuf = 0;
ptChannel->Windex = 0;
ptChannel->Rindex = 0;
ptChannel->filling = 1;
return 0;
}
if ( ! ptChannel->cBuf)
return 0;
if (nRead == 0 && nWrite == 0)
return ptChannel->nBuf;
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;
//if (num != nWrite)
// QuiskPrintf("Circ nWrite %d %d %d %d\n", num, nWrite, ptChannel->nBuf, ptChannel->sizeBuf);
}
else if (nRead > 0) {
if ( ! ptChannel->filling) { // 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;
}
+2101
View File
File diff suppressed because it is too large Load Diff
+337
View File
@@ -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 */