diff --git a/README.md b/README.md
index 2862ad6..4217abe 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ the commands I used to install the needed packages:
```
git clone https://github.com/w9cf/cwsim.git
sudo apt-get install python3-matplotlib
-sudo apt-get install python3-pyqt5
+sudo apt-get install python3-pyqt6
sudo apt-get install libportaudio2
pip install pip --upgrade --user
python3 -m pip install numpy==1.23 --user
diff --git a/cwsim.txt b/cwsim.txt
new file mode 100644
index 0000000..c42cc70
--- /dev/null
+++ b/cwsim.txt
@@ -0,0 +1,22 @@
+P55L Report cwsim (DX Expedition) 24/06/2026 20:04:46
+Durata 00:09:41
+Durata (QSO) 36
+Velocità CW 32 WPM
+Condizioni QSY Difficoltà
+Attività 14
+Durata 15
+Punti totali 36
+Prefissi totali 34
+Punteggio totale 1224
+Punti verificati 34
+Prefissi verificati 32
+Punteggio verificato 1088
+Percentuale d'errore 5.6
+QSO per ora, suddiviso in intervalli da 5 minuti
+0-4 216
+5-9 216
+10-14 0
+Nominativi copiati male
+N1QEY PD9RW
+Stazioni che hanno abbandonato senza terminare il QSO
+SE6N W3PRL
diff --git a/plan.md b/plan.md
new file mode 100644
index 0000000..610dd67
--- /dev/null
+++ b/plan.md
@@ -0,0 +1,14 @@
+# Piano di Sviluppo Cwsim - Prossime Versioni
+
+## UI & Accessibilità
+- [ ] **Revisione Grid Layout Parametri**: Verificare il posizionamento dei nuovi parametri DX (Straight Key, My RST Speed-up, ecc.). Attualmente NVDA li riporta in fondo alla lista.
+- [ ] **Tab Order**: Ottimizzare l'ordine dei tab affinché ogni Label sia seguita immediatamente dalla sua SpinBox, migliorando l'esperienza per utenti con screen reader.
+- [ ] **Verifica Visiva**: Confermare se il layout è corretto anche visivamente o se i nuovi widget sono stati accodati in modo disordinato.
+
+## Report & Logica DX
+- [ ] **Pulizia Report in Modalità Expedition**: In modalità DX Expedition, rimuovere o nascondere i riferimenti ai "progressivi" (numeri seriali) nel report finale (`cwsim.txt`), dato che lo scambio prevede solo l'RST.
+- [ ] **RST Validation**: Assicurarsi che la validazione dei dati nel report rifletta correttamente lo scambio 5NN unico della modalità DX.
+
+## Manutenzione
+- [ ] **Compilazione Traduzioni**: Produrre il file `.qm` aggiornato non appena l'ambiente dispone di `lrelease` o caricarlo manualmente.
+- [ ] **Sincronizzazione Versioning**: Coordinarsi con Kevin per la numerazione ufficiale post-1.0.2.
diff --git a/python/Makefile b/python/Makefile
index 80f3815..965726a 100644
--- a/python/Makefile
+++ b/python/Makefile
@@ -1,11 +1,8 @@
-.PHONY: pyqt6 pyqt5 cwsim.exe
+.PHONY: pyqt6 cwsim.exe
cwsimgui.py: cwsimgui.ui
pyuic6 -i 3 -o cwsimgui.py cwsimgui.ui
-pyqt5:
- pyuic5 -i 3 -o cwsimgui.py cwsimgui.ui
-
pyqt6:
pyuic6 -i 3 -o cwsimgui.py cwsimgui.ui
diff --git a/python/audioprocess.py b/python/audioprocess.py
index 5060f37..01c81fd 100644
--- a/python/audioprocess.py
+++ b/python/audioprocess.py
@@ -14,7 +14,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import sys
import numpy as np
class movavg():
diff --git a/python/contest.py b/python/contest.py
index 9d765f7..44f0d23 100644
--- a/python/contest.py
+++ b/python/contest.py
@@ -26,8 +26,6 @@
from mystation import MyStation
from dxoper import Os
import queue
-import wave
-import time
import sys
import os
import errno
@@ -86,6 +84,11 @@ def __init__(self,rng,inifile=None):
self.savewave = 0
self.saveini = 1
self.savesummary = 1
+ self.isDxExpedition = False
+ self.straightKeyProb = 0.25
+ self.myRstSpeedUp = 0.20
+ self.dxRstSpeedUp = 0.15
+ self.dxRstProb = 0.15
self.fontsize = 12 # not used
self._qskdecayfactor = 1.0/(self._rate*self.qskdecaytime)
@@ -98,7 +101,7 @@ def __init__(self,rng,inifile=None):
self._callList = calllist.CallList(rng)
self.stations = []
self.me = MyStation(self._rng,self._keyer,self,self.call,self.pitch
- ,self.wpm,bufsize=self._bufsize,rate=self._rate)
+ ,self.wpm,bufsize=self._bufsize,rate=self._rate,isDxExpedition=self.isDxExpedition)
self.bufcount = 0
self._rfg0 = 1.0
self._rfg = np.zeros(self._bufsize+1,dtype=np.float64)
@@ -137,7 +140,7 @@ def bandwidth(self):
def bandwidth(self,bandwidth):
self._bandwidth = np.max([np.min([round(bandwidth/50)*50,600]),100])
navg = int(np.rint(0.7*self._rate/self._bandwidth))
- self._fgain = np.sqrt(500/self._bandwidth);
+ self._fgain = np.sqrt(500/self._bandwidth)
self._m1 = movavg(self._bufsize,navg,dtype=np.complex128)
self._m2 = movavg(self._bufsize,navg,dtype=np.complex128)
self._m3 = movavg(self._bufsize,navg,dtype=np.complex128)
@@ -231,7 +234,7 @@ def getAudio(self,outdata,nf,tinfo,status):
if (self.qsy and isinstance(s,DxStation) and
s.oper.state != Os.Done and s.called):
self.q.put(s.myCall)
- if not (self.me.app is None):
+ if self.me.app is not None:
self.me.app.qsy()
self.stations.remove(s)
elif s.state == StationState.Sending:
@@ -271,7 +274,7 @@ def getAudio(self,outdata,nf,tinfo,status):
if isinstance(s,DxStation):
if (s.oper.state == Os.Done):
self.q.put(s.dataToLastQso())
- if not (self.me.app is None):
+ if self.me.app is not None:
self.me.app.lastQso()
# (trueCall, trueRst, trueNr) = s.dataToLastQso()
# print("contest Correct Info ",trueCall,trueRst,trueNr)
@@ -283,10 +286,13 @@ def getAudio(self,outdata,nf,tinfo,status):
lidRstProb=self.lidRstProb,qsb=self.qsb,
flutterProb=self.flutterProb,
rptProb=self.rptProb,fast=self.fast,slow=self.slow,
- isSingle=True,bufsize=self._bufsize,rate=self._rate)
+ straightKeyProb=self.straightKeyProb,
+ isSingle=True,isDxExpedition=self.isDxExpedition,
+ dxRstSpeedUp=self.dxRstSpeedUp, dxRstProb=self.dxRstProb,
+ bufsize=self._bufsize,rate=self._rate)
self.stations.append(s)
s.processEvent(StationEvent.MeFinished)
-
+
# np.savetxt(self.ef,audio)
# self.wf.writeframesraw((audio*30000).astype(np.int16))
@@ -328,7 +334,10 @@ def onMeFinishedSending(self):
lidRstProb=self.lidRstProb,qsb=self.qsb,
flutterProb=self.flutterProb,
rptProb=self.rptProb,fast=self.fast,slow=self.slow,
- isSingle=False,bufsize=self._bufsize,rate=self._rate))
+ straightKeyProb=self.straightKeyProb,
+ isSingle=False,isDxExpedition=self.isDxExpedition,
+ dxRstSpeedUp=self.dxRstSpeedUp, dxRstProb=self.dxRstProb,
+ bufsize=self._bufsize,rate=self._rate))
for s in self.stations:
s.processEvent(StationEvent.MeFinished)
@@ -401,16 +410,21 @@ def readConfig(self,filename):
self.flutter = int(conditionsdict['flutter'])
self.flutterProb = float(conditionsdict['flutterprob'])
self.lids = int(conditionsdict['lids'])
+ self.straightKeyProb = float(conditionsdict.get('straightkeyprob', '0.25'))
self.activity = int(conditionsdict['activity'])
self.lidRstProb = float(conditionsdict['lidrstprob'])
self.lidNrProb = float(conditionsdict['lidnrprob'])
self.rptProb = float(conditionsdict['rptprob'])
+ self.myRstSpeedUp = float(conditionsdict.get('myrstspeedup', '0.20'))
+ self.dxRstSpeedUp = float(conditionsdict.get('dxrstspeedup', '0.15'))
+ self.dxRstProb = float(conditionsdict.get('dxrstprob', '0.15'))
contestdict = dict(p['Contest'])
self.duration = int(contestdict['duration'])
self.mode = eval(contestdict['mode'])
self.savewave = int(contestdict['savewave'])
self.saveini = int(contestdict['saveini'])
self.savesummary = int(contestdict['savesummary'])
+ self.isDxExpedition = contestdict.get('isdxexpedition', 'False') == 'True'
def writeConfig(self,filename):
with open(filename,'w') as f:
@@ -426,10 +440,10 @@ def writeConfig(self,filename):
,'qskdecaytime', 'cwreverse', 'rit', 'monitor']:
p.set('Station',i,str(eval('self.'+i)))
p.add_section('Conditions')
- for i in ['qrn','qrm','tqrm','qsb','flutter','qsy','lids','activity'
- ,'lidRstProb','lidNrProb','rptProb','flutterProb']:
+ for i in ['qrn','qrm','tqrm','qsb','flutter','qsy','lids','straightKeyProb','activity'
+ ,'lidRstProb','lidNrProb','rptProb','flutterProb','myRstSpeedUp','dxRstSpeedUp','dxRstProb']:
p.set('Conditions',i,str(eval('self.'+i)))
p.add_section('Contest')
- for i in ['duration','mode','savewave','saveini','savesummary']:
+ for i in ['duration','mode','savewave','saveini','savesummary','isDxExpedition']:
p.set('Contest',i,str(eval('self.'+i)))
p.write(f)
diff --git a/python/cwsim.py b/python/cwsim.py
index 208b573..ecef6da 100755
--- a/python/cwsim.py
+++ b/python/cwsim.py
@@ -16,15 +16,10 @@
# See https://www.gnu.org/licenses/ for GPL licensing information.
#
import os
-try:
- from PyQt6 import QtCore, QtGui, QtWidgets
- from PyQt6.QtWidgets import QApplication, QTableWidgetItem
- from PyQt6.QtGui import QShortcut
- from PyQt6.uic import compileUi
-except ImportError:
- from PyQt5 import QtCore, QtGui, QtWidgets
- from PyQt5.QtWidgets import QApplication, QTableWidgetItem, QShortcut
- from PyQt5.uic import compileUi
+from PyQt6 import QtCore, QtGui, QtWidgets
+from PyQt6.QtWidgets import QApplication, QTableWidgetItem
+from PyQt6.QtGui import QShortcut
+from PyQt6.uic import compileUi
try:
uifilename = os.path.join(os.path.dirname(__file__),"cwsimgui.ui")
@@ -60,6 +55,9 @@ def validate(self,string,pos):
acceptable, string , pos = super().validate(string,pos)
return (acceptable, string.upper(), pos)
+VERSION = "0.9.0 Beta"
+RELEASE_DATE = "May 4, 2026"
+
class RunApp(QtWidgets.QMainWindow,cwsimgui.Ui_CwsimMainWindow):
advancesig = QtCore.pyqtSignal()
@@ -136,6 +134,7 @@ def __init__(self,parent=None):
self.activitySpinBox.valueChanged.connect(self.activity)
self.durationComboBox.currentIndexChanged.connect(self.modecombo)
self.contestComboBox.currentIndexChanged.connect(self.modecombo)
+ self.typeComboBox.currentIndexChanged.connect(self.typecombo)
self.trF1Button.clicked.connect(self.f1)
self.trF2Button.clicked.connect(self.f2)
self.trF3Button.clicked.connect(self.f3)
@@ -171,8 +170,12 @@ def __init__(self,parent=None):
self.tqrmSpinBox.valueChanged.connect(self.tqrm)
self.lidRstProbSpinBox.valueChanged.connect(self.lidRstProb)
self.lidNrProbSpinBox.valueChanged.connect(self.lidNrProb)
+ self.straightKeyProbSpinBox.valueChanged.connect(self.straightKeyProb)
self.rptProbSpinBox.valueChanged.connect(self.rptProb)
self.flutterProbSpinBox.valueChanged.connect(self.flutterProb)
+ self.myRstSpeedUpSpinBox.valueChanged.connect(self.myRstSpeedUp)
+ self.dxRstSpeedUpSpinBox.valueChanged.connect(self.dxRstSpeedUp)
+ self.dxRstProbSpinBox.valueChanged.connect(self.dxRstProb)
self.fastSpinBox.valueChanged.connect(self.fast)
self.slowSpinBox.valueChanged.connect(self.slow)
self.trExchangeEntry.setEnabled(False)
@@ -184,6 +187,7 @@ def __init__(self,parent=None):
self._goodQsoCount = 0
self._qtimes = []
scdict = { "Alt+W":self.wipe, "Return":self.enter, "Escape":self.escape,
+ "Tab":self.tab, "Shift+Tab":self.backtab,
"F1":self.f1, "F2":self.f2, "F3":self.f3, "F4":self.f4, "F5":self.f5,
"F6":self.f6, "F7":self.f7, "F8":self.f8, "Shift+Up":self.ritup,
"Shift+Down":self.ritdown, "Alt+C":self.ritclear, "Ctrl+Up":self.bwup,
@@ -224,6 +228,9 @@ def __init__(self,parent=None):
self._goodPfxs = set()
self.prefix = Prefix()
self.nrchecked = 0
+ welcome_msg = _translate("RunApp", "Welcome to Cwsim {version} ({date}) by Kevin Schmidt (W9CF) and Gabriele Battaglia (IZ4APU)").format(version=VERSION, date=RELEASE_DATE)
+ self.logTable.insertRow(0)
+ self.logTable.setItem(0, 1, QTableWidgetItem(welcome_msg))
def syncGui(self):
self.action_Update_Default_Configuration_on_Exit.setChecked(
@@ -253,10 +260,17 @@ def syncGui(self):
self.tqrmSpinBox.setValue(self.contest.tqrm)
self.lidRstProbSpinBox.setValue(self.contest.lidRstProb)
self.lidNrProbSpinBox.setValue(self.contest.lidNrProb)
+ self.straightKeyProbSpinBox.setValue(self.contest.straightKeyProb)
+ self.myRstSpeedUpSpinBox.setValue(self.contest.myRstSpeedUp)
+ self.dxRstSpeedUpSpinBox.setValue(self.contest.dxRstSpeedUp)
+ self.dxRstProbSpinBox.setValue(self.contest.dxRstProb)
self.rptProbSpinBox.setValue(self.contest.rptProb)
self.flutterProbSpinBox.setValue(self.contest.flutterProb)
self.fastSpinBox.setValue(self.contest.fast)
self.slowSpinBox.setValue(self.contest.slow)
+ is_dx = getattr(self.contest, 'isDxExpedition', False)
+ self.typeComboBox.setCurrentIndex(1 if is_dx else 0)
+ self.nrEntry.setEnabled(not is_dx)
if self.contest.mode == RunMode.pileup:
self.contestComboBox.setCurrentIndex(0)
self.durationComboBox.setCurrentIndex(0)
@@ -311,8 +325,9 @@ def writeSummary(self,filename):
with open(filename,mode,encoding='utf8') as f:
if self.contest.savesummary == 2:
f.write("\n")
+ mode_str = "DX Expedition" if getattr(self.contest, 'isDxExpedition', False) else "Contest"
s = (self.contest.call + " " + _translate("RunApp","cwsim summary")
- + " " + datetime.datetime.now().strftime("%c") + "\n")
+ + " (" + mode_str + ") " + datetime.datetime.now().strftime("%c") + "\n")
f.write(s)
sec = int(self.contest.seconds)
h = sec//3600
@@ -392,7 +407,8 @@ def writeSummary(self,filename):
chk = self.logTable.item(i,cols-1).text()
if chk == "NIL":
nil.append(self.logTable.item(i,1).text())
- if chk.count("NR") != 0 or chk.count("RST") != 0:
+ is_dx = getattr(self.contest, 'isDxExpedition', False)
+ if chk.count("RST") != 0 or (not is_dx and chk.count("NR") != 0):
ex.append((self.logTable.item(i,1).text(),
self.logTable.item(i,2).text(),chk))
if chk == "QSY":
@@ -592,14 +608,15 @@ def startStop(self):
sc.setEnabled(True)
def about(self):
- version = "Testing version"
+ version = VERSION
msg = """
- Python CW Simulator {}
+ Python CW Simulator {} ({})
Copyright 2022, Kevin E. Schmidt, W9CF, w9cf@arrl.net
+ Authors: Kevin Schmidt (W9CF) and Gabriele Battaglia (IZ4APU)
Based on and derivative of Morse Runner
Copyright 2004-2006, Alex Shovkoplyas, VE3NEA
- ve3nea@dxatlast.com""".format(version)
+ ve3nea@dxatlast.com""".format(version, RELEASE_DATE)
QtWidgets.QMessageBox.about(self,"CW Simulator",msg)
def shortcutHelp(self):
@@ -683,6 +700,9 @@ def flutter(self,s):
def lids(self,s):
self.contest.lids = (s // 2)
+ def typecombo(self,s):
+ self.contest.isDxExpedition = (s == 1)
+
def modecombo(self,s):
i = self.contestComboBox.currentIndex()
j = self.durationComboBox.currentIndex()
@@ -732,6 +752,20 @@ def lidRstProb(self,s):
def lidNrProb(self,s):
self.contest.lidNrProb = s
+ def straightKeyProb(self,s):
+ self.contest.straightKeyProb = s
+
+ def myRstSpeedUp(self,s):
+ self.contest.myRstSpeedUp = s
+ if self.contest.me is not None:
+ self.contest.me.myRstSpeedUp = s
+
+ def dxRstSpeedUp(self,s):
+ self.contest.dxRstSpeedUp = s
+
+ def dxRstProb(self,s):
+ self.contest.dxRstProb = s
+
def rptProb(self,s):
self.contest.rptProb = s
@@ -777,13 +811,15 @@ def period(self):
def semicolon(self):
self.sendMsg(StationMessage.HisCall)
- self.sendMsg(StationMessage.NR)
+ if not getattr(self.contest, 'isDxExpedition', False):
+ self.sendMsg(StationMessage.NR)
def f1(self):
self.sendMsg(StationMessage.CQ)
def f2(self):
- self.sendMsg(StationMessage.NR)
+ if not getattr(self.contest, 'isDxExpedition', False):
+ self.sendMsg(StationMessage.NR)
def f3(self):
self.sendMsg(StationMessage.TU)
@@ -812,6 +848,76 @@ def f8(self):
def entrytabs(self):
self.tr = self.entryTabs.currentIndex() == 1
+ def space(self):
+ if self.tr: return
+ self._mustAdvance = False
+ foc = QtWidgets.QApplication.focusWidget()
+ is_dx = getattr(self.contest, 'isDxExpedition', False)
+ if foc is self.callEntry:
+ if self._rst == '':
+ self.rstEntry.setText('599')
+ else:
+ self.rstEntry.deselect()
+ if is_dx:
+ self.rstEntry.setFocus()
+ else:
+ self.nrEntry.setFocus()
+ elif foc in [self.rstEntry, self.nrEntry]:
+ self.callEntry.setFocus()
+ else:
+ self.callEntry.setFocus()
+
+ def tab(self):
+ foc = QtWidgets.QApplication.focusWidget()
+ is_dx = getattr(self.contest, 'isDxExpedition', False)
+ if foc is self.callEntry:
+ self.rstEntry.setFocus()
+ if len(self._rst) == 3:
+ self.rstEntry.setSelection(1,1)
+ elif foc == self.rstEntry:
+ if is_dx:
+ self.callEntry.setFocus()
+ i = self._hiscall.find('?')
+ if i>= 0:
+ self.callEntry.setSelection(i,1)
+ else:
+ self.callEntry.deselect()
+ self.callEntry.end(False)
+ else:
+ self.nrEntry.setFocus()
+ self.nrEntry.deselect()
+ elif foc == self.nrEntry:
+ self.callEntry.setFocus()
+ i = self._hiscall.find('?')
+ if i>= 0:
+ self.callEntry.setSelection(i,1)
+ else:
+ self.callEntry.deselect()
+ self.callEntry.end(False)
+ elif foc is self.trCallEntry:
+ self.trExchangeEntry.setFocus()
+ elif foc is self.trExchangeEntry:
+ self.trCallEntry.setFocus()
+
+ def backtab(self):
+ foc = QtWidgets.QApplication.focusWidget()
+ is_dx = getattr(self.contest, 'isDxExpedition', False)
+ if foc is self.rstEntry:
+ self.callEntry.setFocus()
+ self.callEntry.end(False)
+ elif foc is self.nrEntry:
+ self.rstEntry.setFocus()
+ if len(self._rst) == 3:
+ self.rstEntry.setSelection(1,1)
+ elif foc is self.callEntry:
+ if is_dx:
+ self.rstEntry.setFocus()
+ if len(self._rst) == 3:
+ self.rstEntry.setSelection(1,1)
+ else:
+ self.nrEntry.setFocus()
+ self.nrEntry.deselect()
+
def enter(self):
if self.tr:
self.trCallEntry.setText(self._hiscall)
@@ -819,20 +925,30 @@ def enter(self):
if self._hiscall == '':
self.sendMsg(StationMessage.CQ)
else:
+ dx_exp = getattr(self.contest, 'isDxExpedition', False)
c = self._callsent
n = self._nrsent
r = self._nr != ""
- if (not c) or ((not n) and (not r)):
- self.sendMsg(StationMessage.HisCall)
- if not n:
- self.sendMsg(StationMessage.NR)
- if n and not r:
- self.sendMsg(StationMessage.Qm)
- if r and (c or n):
- self.sendMsg(StationMessage.TU)
- self.saveQso()
+
+ if dx_exp:
+ if not c:
+ self.sendMsg(StationMessage.HisCall)
+ self.sendMsg(StationMessage.NR) # Sends RST in DX mode
+ else:
+ self.sendMsg(StationMessage.TU)
+ self.saveQso()
else:
- self._mustAdvance = True
+ if (not c) or ((not n) and (not r)):
+ self.sendMsg(StationMessage.HisCall)
+ if not n:
+ self.sendMsg(StationMessage.NR)
+ if n and not r:
+ self.sendMsg(StationMessage.Qm)
+ if r and (c or n):
+ self.sendMsg(StationMessage.TU)
+ self.saveQso()
+ else:
+ self._mustAdvance = True
def qsy(self):
self.qsysig.emit()
@@ -865,12 +981,11 @@ def advanceslot(self):
self.rstEntry.setText('599')
else:
self.rstEntry.deselect()
- if self._hiscall.find('?') == -1:
+ if self._hiscall.find('?') == -1 and not getattr(self.contest, 'isDxExpedition', False):
self.nrEntry.setFocus()
else:
self.callEntry.setFocus()
self._mustAdvance = False
-
def lastQso(self):
self.lastqsosig.emit()
@@ -943,23 +1058,31 @@ def sendMsg(self,msg):
def checkQso(self):
if self._lastLog[0] != self._lastQso[0]: return "NIL"
- if self._lastLog[1] != self._lastQso[1]: return "NR"
- if self._lastLog[2] != self._lastQso[2]: return "RST"
+ if getattr(self.contest, 'isDxExpedition', False):
+ if self._lastLog[2] != self._lastQso[2]: return "RST"
+ else:
+ if self._lastLog[1] != self._lastQso[1]: return "NR"
+ if self._lastLog[2] != self._lastQso[2]: return "RST"
return ""
def saveQso(self):
time.sleep(0) #yield
#check needed if period or equivalent typed before QSO info set up
- if not (self._hiscall and self._nr and self._rst):
- return
+ dx_exp = getattr(self.contest, 'isDxExpedition', False)
+ if self._rst == "":
+ self._rst = "599"
+ if dx_exp:
+ if not self._hiscall:
+ return
+ else:
+ if not (self._hiscall and self._nr):
+ return
self._nrsent = False
self._callsent = False
self._rawQsoCount += 1
h,m,s = self.contest.time()
- if self._rst == "":
- self._rst = "599"
- self._lastLog = [self._hiscall, int(self._nr), int(self._rst)]
+ self._lastLog = [self._hiscall, int(self._nr) if self._nr else 0, int(self._rst)]
time.sleep(0) #yield
rawPfx = self.prefix.getPrefix(self._hiscall)
time.sleep(0) #yield
@@ -987,8 +1110,12 @@ def saveQso(self):
self._lastLog = [None,None,None]
self._lastQso = [None,None,None]
tstr = '{:02d}:{:02d}:{:02d}'.format(h,m,s)
- rcvd = '{:03d} {:04d}'.format(int(self._rst),int(self._nr))
- sent = '{:03d} {:04d}'.format(599,self.contest.me.nr)
+ if dx_exp:
+ rcvd = '{:03d}'.format(int(self._rst))
+ sent = '{:03d}'.format(599)
+ else:
+ rcvd = '{:03d} {:04d}'.format(int(self._rst),int(self._nr))
+ sent = '{:03d} {:04d}'.format(599,self.contest.me.nr)
time.sleep(0) #yield
r = self.logTable.rowCount()
time.sleep(0) #yield
@@ -1042,6 +1169,9 @@ def wipe(self):
self.rstEntry.setText("")
self.nrEntry.setText("")
self.callEntry.setFocus()
+ self._hiscall = ""
+ self._rst = ""
+ self._nr = ""
self._callsent = False
self._nrsent = False
@@ -1103,41 +1233,6 @@ def downarrow(self):
else:
self.ritdown()
- def space(self):
- if self.tr: return
- self._mustAdvance = False
- foc = QtWidgets.QApplication.focusWidget()
- if foc in [self.callEntry, self.rstEntry]:
- if self._rst == '':
- self.rstEntry.setText('599')
- else:
- self.rstEntry.deselect()
- self.nrEntry.setFocus()
- else:
- self.callEntry.setFocus()
-
- def tab(self):
- foc = QtWidgets.QApplication.focusWidget()
- if foc is self.callEntry:
- self.rstEntry.setFocus()
- if len(self._rst) == 3:
- self.rstEntry.setSelection(1,1)
- elif foc == self.rstEntry:
- self.nrEntry.setFocus()
- self.nrEntry.deselect()
- elif foc == self.nrEntry:
- self.callEntry.setFocus()
- i = self._hiscall.find('?')
- if i>= 0:
- self.callEntry.setSelection(i,1)
- else:
- self.callEntry.deselect()
- self.callEntry.end(False)
- elif foc is self.trCallEntry:
- self.trExchangeEntry.setFocus()
- elif foc is self.trExchangeEntry:
- self.trCallEntry.setFocus()
-
def close(self):
if not os.path.exists(self.defaultini) or self.contest.saveini != 0:
self.contest.writeConfig(self.defaultini)
@@ -1161,6 +1256,11 @@ def close(self):
tdir = os.path.join(tdir,'translate')
translator.load(tfile,tdir)
app.installTranslator(translator)
+ _translate = QtCore.QCoreApplication.translate
+ print(_translate("RunApp", "Welcome to Cwsim - Python CW Simulator"))
+ print(_translate("RunApp", "Authors: Kevin Schmidt (W9CF) and Gabriele Battaglia (IZ4APU)"))
+ print(_translate("RunApp", "Version: {version}").format(version=VERSION))
+ print(_translate("RunApp", "Date: {date}").format(date=RELEASE_DATE))
form = RunApp()
form.show()
app.exec()
diff --git a/python/cwsimgui.ui b/python/cwsimgui.ui
index 9ecb078..4bd5f45 100644
--- a/python/cwsimgui.ui
+++ b/python/cwsimgui.ui
@@ -1,4 +1,4 @@
-
+
CwsimMainWindow
@@ -30,7 +30,7 @@
Qt::TabFocus
-
+
Log
@@ -101,15 +101,15 @@
false
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -133,7 +133,7 @@
Qt::NoFocus
-
+
-
@@ -810,6 +810,92 @@
+ -
+
+
+ Straight Key %
+
+
+
+ -
+
+
+ My RST Speed Up %
+
+
+
+ -
+
+
+ Bot RST Speed Up %
+
+
+
+ -
+
+
+ Bot Speed Up Prob %
+
+
+
+ -
+
+
+ Qt::StrongFocus
+
+
+ Probability of stations using a straight key (0 to 1)
+
+
+ Straight Key probability
+
+
+ 1.000000000000000
+
+
+ 0.010000000000000
+
+
+
+ -
+
+
+ My RST speed increase (0 to 1)
+
+
+ 1.000000000000000
+
+
+ 0.010000000000000
+
+
+
+ -
+
+
+ Bot RST speed increase (0 to 1)
+
+
+ 1.000000000000000
+
+
+ 0.010000000000000
+
+
+
+ -
+
+
+ Probability of bots using speed up (0 to 1)
+
+
+ 1.000000000000000
+
+
+ 0.010000000000000
+
+
+
@@ -1303,7 +1389,7 @@
Qt::NoFocus
-
+
-
@@ -1440,7 +1526,7 @@
-
+ - Qt::TabFocusChoose operating modeOperating Mode
- Contest
- DX Expedition
-
@@ -1538,11 +1624,11 @@
false
-
-
-
-
-
+
+
+
+
+
@@ -1621,15 +1707,15 @@
&File
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
toolBar
@@ -1779,7 +1865,7 @@
qsyCheck
qrmCheck
lidsCheck
- lidRstProbSpinBox
+ straightKeyProbSpinBoxlidRstProbSpinBox
rptProbSpinBox
fastSpinBox
qsbCheck
@@ -1789,7 +1875,7 @@
flutterProbSpinBox
slowSpinBox
contestComboBox
- startStopButton
+ typeComboBoxstartStopButton
durationComboBox
durationSpinBox
activitySpinBox
@@ -1800,7 +1886,7 @@
trCallEntry
trExchangeEntry
-
+
action_Exit
@@ -1851,4 +1937,4 @@
-
+
\ No newline at end of file
diff --git a/python/dxoper.py b/python/dxoper.py
index a2cef22..5450f00 100644
--- a/python/dxoper.py
+++ b/python/dxoper.py
@@ -47,7 +47,7 @@ class DxOperator():
FULL_PATIENCE = 5
def __init__(self,rng,minutes=0,cqstn=None,call=None,skills=2,
s2bfac=11025/512,lids=True,rptProb=0.1,wpm=40,fast=1.1,slow=0.9,
- isSingle=False,state=Os.NeedPrevEnd):
+ isSingle=False,isDxExpedition=False,state=Os.NeedPrevEnd):
"""
Arguments
rng: numpy random number generator
@@ -57,6 +57,7 @@ def __init__(self,rng,minutes=0,cqstn=None,call=None,skills=2,
call: My station's call
skills: My skills
isSingle: True if RunMode is single calls
+ isDxExpedition: True if in DX Expedition mode
state: Initial operator state
"""
self._rng = rng
@@ -69,12 +70,14 @@ def __init__(self,rng,minutes=0,cqstn=None,call=None,skills=2,
self.repeatCnt= None
self._minutes = minutes
self._isSingle = isSingle
+ self.isDxExpedition = isDxExpedition
self._lids = lids
self._wpm = wpm
self._slow = slow
self._fast = fast
self._rptProb = rptProb
self._s2bfac = s2bfac
+ self.rstSent = False
def getSendDelay(self):
"""
@@ -152,7 +155,7 @@ def ismycall(self):
for y in range(1,len(c0)+1):
d = m[x-1,y-1]
if c[x-1] != c0[y-1]: d += 1
- m[x,y] = np.min([m[x,y-1],m[x-1,y]+1,d])
+ m[x,y] = np.min([m[x,y-1],m[x-1,y],d])
else:
for y in range(1,len(c0)+1):
m[x,y] = np.min([m[x,y-1],m[x-1,y],m[x-1,y-1]])
@@ -208,12 +211,18 @@ def msgReceived(self,msgs):
isme = self.ismycall()
if isme == Mc.Yes:
if self.state in [Os.NeedPrevEnd, Os.NeedQso, Os.NeedCallNr]:
- self.setState(Os.NeedNr)
+ if self.isDxExpedition:
+ self.setState(Os.NeedEnd)
+ else:
+ self.setState(Os.NeedNr)
elif self.state == Os.NeedCall:
self.setState(Os.NeedEnd)
elif isme == Mc.Almost:
if self.state in [Os.NeedPrevEnd, Os.NeedQso, Os.NeedNr]:
- self.setState(Os.NeedCallNr)
+ if self.isDxExpedition:
+ self.setState(Os.NeedCall)
+ else:
+ self.setState(Os.NeedCallNr)
elif self.state == Os.NeedEnd:
self.setState(Os.NeedCall)
elif isme == Mc.No:
@@ -258,7 +267,7 @@ def getReply(self):
return reply message.
"""
if self.state in [Os.NeedPrevEnd, Os.Done, Os.Failed]:
- res = StationMessage.noMsg
+ res = StationMessage.NoMsg
elif self.state == Os.NeedQso:
res = StationMessage.MyCall
elif self.state == Os.NeedNr:
@@ -281,11 +290,31 @@ def getReply(self):
else:
res = StationMessage.DeMyCall2
else: #NeedEnd
- if (self.patience == (DxOperator.FULL_PATIENCE-1)
- or self._rng.random() < 0.9):
- res = StationMessage.R_NR
+ if self.isDxExpedition:
+ if not self.rstSent:
+ res = StationMessage.R_NR # Send RST if not sent yet
+ else:
+ # RST already sent, send final greetings
+ r = self._rng.random()
+ if r < 0.4:
+ res = StationMessage.TU
+ elif r < 0.6:
+ res = StationMessage.NoMsg
+ elif r < 0.8:
+ res = StationMessage.NoMsg # Silence is common
+ else:
+ res = StationMessage.TU
else:
- res = StationMessage.R_NR2
+ if (self.patience == (DxOperator.FULL_PATIENCE-1)
+ or self._rng.random() < 0.9):
+ res = StationMessage.R_NR
+ else:
+ res = StationMessage.R_NR2
+
+ # Track if RST was sent in this reply
+ if res in [StationMessage.R_NR, StationMessage.R_NR2,
+ StationMessage.DeMyCallNr1, StationMessage.DeMyCallNr2,
+ StationMessage.MyCallNr2, StationMessage.NR]:
+ self.rstSent = True
return res
-
diff --git a/python/dxstation.py b/python/dxstation.py
index b9c2e8c..57e1c30 100644
--- a/python/dxstation.py
+++ b/python/dxstation.py
@@ -27,8 +27,23 @@ class DxStation(station.Station):
def __init__(self,rng,keyer,callList,cqstn,minutes=0,
lids=True,lidNrProb=0.1,lidRstProb=0.03,qsb=True,flutterProb=0.3,
rptProb=0.1,fast=1.1,slow=0.9,
- isSingle=False,bufsize=512,rate=11025):
- super().__init__(rng,keyer,bufsize=bufsize,rate=rate)
+ straightKeyProb=0.25,
+ isSingle=False,isDxExpedition=False,
+ dxRstSpeedUp=0.15, dxRstProb=0.15,
+ bufsize=512,rate=11025):
+ super().__init__(rng,keyer,bufsize=bufsize,rate=rate,isDxExpedition=isDxExpedition)
+ self.myRstSpeedUp = dxRstSpeedUp
+ if self._rng.random() < straightKeyProb:
+ while True:
+ l = self._rng.integers(low=20, high=43)
+ p = self._rng.integers(low=18, high=55)
+ s = self._rng.integers(low=25, high=76)
+ if (l <= 24 and p <= 25 and s <= 35) or (l >= 38 and p >= 47 and s >= 65):
+ continue
+ self.l = l
+ self.p = p
+ self.s = s
+ break
self.cqstn = cqstn
self.hisCall = self.cqstn.myCall
self.myCall = callList.pickCall()
@@ -37,7 +52,6 @@ def __init__(self,rng,keyer,callList,cqstn,minutes=0,
rng,
minutes,
call=self.myCall,
- skills=rng.integers(low=1,high=4),
s2bfac=rate/bufsize,
lids=lids,
rptProb=rptProb,
@@ -45,11 +59,14 @@ def __init__(self,rng,keyer,callList,cqstn,minutes=0,
fast=fast,
slow=slow,
isSingle = isSingle,
+ isDxExpedition = isDxExpedition,
state=Os.NeedPrevEnd,
cqstn=cqstn)
self.nrWithError = lids and (self._rng.random() < lidNrProb)
self.wpm = self.oper.getWpm()
self.nr = self.oper.getNr()
+ self.nrChecked = 0
+ self.speedUpRst = isDxExpedition and (self._rng.random() < dxRstProb)
if lids and self._rng.random() < lidRstProb:
self._rst = 559+10*self._rng.integers(4)
else:
diff --git a/python/keyer.py b/python/keyer.py
index 18df9a8..d7ca91b 100644
--- a/python/keyer.py
+++ b/python/keyer.py
@@ -16,8 +16,6 @@
# along with this program. If not, see .
import numpy as np
import math
-import configparser
-import os
class Keyer():
"""
@@ -34,7 +32,7 @@ class Keyer():
"?":"..--..", "/":"-..-.", ";":"-.-.-.", "(":"-.--.", "[":"-.--.",
")":"-.--.-", "]":"-.--.-", "@":".--.-.", "*":"...-.-", "+":".-.-.",
"%":".-...", ":":"---...", "=":"-...-", '"':".-..-.", "'":".----.",
- "!":"---.", "$":"...-..-"," ":"", "_":""
+ "!":"---.", "$":"...-..-"," ":"", "_":"", ">":"", "<":""
})
def __init__(self,rate=11025,bufsize=512,risetime=0.005):
@@ -72,42 +70,95 @@ def encode(self,txt):
string encoding for morse dits and dahs
"""
s = ""
- for i in range(len(txt)-1):
- s += Keyer._morse[txt[i]] + " "
- s += Keyer._morse[txt[len(txt)-1]]
+ for i in range(len(txt)):
+ char = txt[i]
+ if char in [">", "<"]:
+ s += char
+ elif char in Keyer._morse:
+ s += Keyer._morse[char]
+ # Add space only if not the last char and next is not a speed marker
+ if i < len(txt) - 1 and txt[i+1] not in [">", "<"]:
+ s += " "
if s != "":
s += "~"
return s
- def getenvelop(self,msg,wpm):
+ def getenvelop(self,msg,wpm,l=30,s=50,p=50,speed_up_factor=0.20):
"""
Arguments
msg: morse encoding of dits and dahs
wpm: speed in words per minute (PARIS)
+ l: dash weight (default 30)
+ s: space weight (default 50)
+ p: dot weight (default 50)
+ speed_up_factor: how much to increase speed (0 to 1)
Returns
keying envelop for audio samples
"""
nr = len(self.rise)
- count = 2*(msg.count('.')+msg.count(' ')+2*msg.count('-'))+msg.count('~')
- samples = int(np.rint(1.2*self.rate/wpm))
- n = int(self._bufsize*np.ceil((count*samples+nr)/self._bufsize))
+
+ def get_params(current_wpm):
+ T = 1.2 * self.rate / current_wpm
+ d_on = int(np.rint(T * (p / 50.0)))
+ da_on = int(np.rint(3.0 * T * (l / 30.0)))
+ i_off = int(np.rint(T * (s / 50.0)))
+ l_gap = int(np.rint(3.0 * T * (s / 50.0))) - i_off
+ pad = int(np.rint(T))
+ return d_on, da_on, i_off, l_gap, pad
+
+ # First pass: Calculate total length
+ total_samples = 0
+ cur_wpm = wpm
+ for char in msg:
+ if char == '>':
+ cur_wpm = wpm * (1.0 + speed_up_factor)
+ continue
+ elif char == '<':
+ cur_wpm = wpm
+ continue
+
+ d_on, da_on, i_off, l_gap, pad = get_params(cur_wpm)
+ if char == '.':
+ total_samples += d_on + i_off
+ elif char == '-':
+ total_samples += da_on + i_off
+ elif char == ' ':
+ total_samples += l_gap
+ elif char == '~':
+ total_samples += pad
+
+ n = int(self._bufsize*np.ceil((total_samples + nr + 100)/self._bufsize))
env = np.zeros(n,dtype=np.float32)
- dit = np.ones(nr+samples,dtype=np.float32)
- dit[:nr] = self.rise
- dit[samples:] = self.fall
- dah = np.ones(nr+3*samples,dtype=np.float32)
- dah[:nr] = self.rise
- dah[3*samples:] = self.fall
+
k = 0
- for i in range(len(msg)):
- if msg[i] == '.':
- env[k:k+len(dit)] = dit
- k += 2*samples
- elif msg[i] == '-':
- env[k:k+len(dah)] = dah
- k += 4*samples
- elif msg[i] == ' ':
- k += 2*samples-nr
- elif msg[i] == '~':
- k += samples-nr
+ cur_wpm = wpm
+ for char in msg:
+ if char == '>':
+ cur_wpm = wpm * (1.0 + speed_up_factor)
+ continue
+ elif char == '<':
+ cur_wpm = wpm
+ continue
+
+ d_on, da_on, i_off, l_gap, pad = get_params(cur_wpm)
+
+ if char == '.':
+ pulse = np.ones(nr+d_on,dtype=np.float32)
+ pulse[:nr] = self.rise
+ pulse[d_on:] = self.fall
+ if k+len(pulse) <= n:
+ env[k:k+len(pulse)] = pulse
+ k += d_on + i_off
+ elif char == '-':
+ pulse = np.ones(nr+da_on,dtype=np.float32)
+ pulse[:nr] = self.rise
+ pulse[da_on:] = self.fall
+ if k+len(pulse) <= n:
+ env[k:k+len(pulse)] = pulse
+ k += da_on + i_off
+ elif char == ' ':
+ k += l_gap
+ elif char == '~':
+ k += pad
+
return env
diff --git a/python/mplwidget.py b/python/mplwidget.py
index 40b4454..a3bbabe 100644
--- a/python/mplwidget.py
+++ b/python/mplwidget.py
@@ -14,15 +14,10 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-try:
- from PyQt6 import QtWidgets
- from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as Canvas
-except ImportError:
- from PyQt5 import QtWidgets
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as Canvas
+from PyQt6 import QtWidgets
+from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as Canvas
from matplotlib.figure import Figure
-import matplotlib
import numpy as np
class MplCanvas(Canvas):
diff --git a/python/mystation.py b/python/mystation.py
index 1c2d604..c4f8931 100644
--- a/python/mystation.py
+++ b/python/mystation.py
@@ -19,14 +19,15 @@
from station import StationMessage
class MyStation(station.Station):
- def __init__(self,rng,keyer,contest,myCall,pitch,wpm,bufsize=512,rate=11025):
- super().__init__(rng,keyer,bufsize=bufsize,rate=rate)
+ def __init__(self,rng,keyer,contest,myCall,pitch,wpm,bufsize=512,rate=11025,isDxExpedition=False):
+ super().__init__(rng,keyer,bufsize=bufsize,rate=rate,isDxExpedition=isDxExpedition)
self._contest = contest
self.myCall = myCall
self.nr = 1
self._rst = 599
self.pitch = pitch
self.wpm = wpm
+ self.speedUpRst = isDxExpedition
self._amplitude = 1.0
self._pieces = []
self.app = None
@@ -71,7 +72,7 @@ def getBuffer(self):
self._pieces.pop(0)
if len(self._pieces) > 0:
self.sendNextPiece()
- if not (self.app is None):
+ if self.app is not None:
self.app.advance()
return buf
@@ -84,7 +85,7 @@ def updateCallInMessage(self,call): #check if sound thread problem?
res = False
if res:
s = self._keyer.encode(call.lower())
- ne = self._keyer.getenvelop(s,self.wpm)*self._amplitude
+ ne = self._keyer.getenvelop(s,self.wpm,self.l,self.s,self.p)*self._amplitude
res = len(ne) >= self._sendpos
if res:
res = np.array_equiv(self._envelop[0:self._sendpos]
diff --git a/python/station.py b/python/station.py
index 5f5846b..9b7e4b3 100644
--- a/python/station.py
+++ b/python/station.py
@@ -94,7 +94,7 @@ class Station():
StationMessage.AGN: 'AGN'
}
- def __init__(self,rng,keyer,bufsize=512,rate=11025):
+ def __init__(self,rng,keyer,bufsize=512,rate=11025,isDxExpedition=False):
"""
Arguments:
rng: a numpy random number generator
@@ -110,8 +110,14 @@ def __init__(self,rng,keyer,bufsize=512,rate=11025):
self._timeout = NEVER
self._amplitude = 0.7
self.wpm = 30
+ self.l = 30
+ self.s = 50
+ self.p = 50
self._rst = 599
self.nr = 1
+ self.isDxExpedition = isDxExpedition
+ self.speedUpRst = False
+ self.myRstSpeedUp = 0.20
self.nrWithError = False
self.myCall = ''
self.hisCall = ''
@@ -154,7 +160,7 @@ def sendText(self,msg):
else:
self._msgtext = msg
s = self._keyer.encode(self._msgtext.lower())
- self._envelop = self._keyer.getenvelop(s,self.wpm)*self._amplitude
+ self._envelop = self._keyer.getenvelop(s,self.wpm,self.l,self.s,self.p,speed_up_factor=self.myRstSpeedUp)*self._amplitude
self.state = StationState.Sending
self._timeout = NEVER
@@ -165,7 +171,11 @@ def sendMsg(self,stationmsg):
self.state = StationState.Listening
else:
self.msgs.append(stationmsg)
- self.sendText(Station.msg2txt[stationmsg])
+ msg_text = Station.msg2txt[stationmsg]
+ if self.isDxExpedition:
+ if stationmsg == StationMessage.CQ:
+ msg_text = 'CQ DE UP'
+ self.sendText(msg_text)
def tick(self):
if self.state == StationState.Sending and self._envelop is None:
@@ -178,8 +188,13 @@ def tick(self):
self.processEvent(StationEvent.Timeout)
def nrAsText(self):
- s = '{:d}{:03d}'.format(int(self._rst),int(self.nr))
- if self.nrWithError:
+ if self.isDxExpedition:
+ s = '{:d}'.format(int(self._rst))
+ else:
+ s = '{:d}{:03d}'.format(int(self._rst),int(self.nr))
+ if self.speedUpRst:
+ s = '>' + s + '<'
+ if self.nrWithError and not self.isDxExpedition:
if s[-1] in ['2','3','4','5','6','7']:
if self._rng.random() < 0.5:
s = '{:d}{:03d}{:s}{:03d}'.format(
diff --git a/python/translate/it_IT.ts b/python/translate/it_IT.ts
index abe7530..4bef0ef 100644
--- a/python/translate/it_IT.ts
+++ b/python/translate/it_IT.ts
@@ -698,6 +698,42 @@
Duration (QSOs)
Durata (QSO)
+
+ Straight Key %
+ Tasto verticale %
+
+
+ My RST Speed Up %
+ Aumento velocità mio RST %
+
+
+ Bot RST Speed Up %
+ Aumento velocità RST Bot %
+
+
+ Bot Speed Up Prob %
+ Prob. aumento velocità Bot %
+
+
+ Straight Key probability
+ Probabilità tasto verticale
+
+
+ Probability of stations using a straight key (0 to 1)
+ Probabilità che le stazioni usino un tasto verticale (da 0 a 1)
+
+
+ My RST speed increase (0 to 1)
+ Incremento velocità del mio RST (da 0 a 1)
+
+
+ Bot RST speed increase (0 to 1)
+ Incremento velocità dell'RST dei Bot (da 0 a 1)
+
+
+ Probability of bots using speed up (0 to 1)
+ Probabilità che i bot usino la velocità aumentata (da 0 a 1)
+
MplCanvas
@@ -889,6 +925,26 @@
QSOs/Hour for each 5 minute interval
QSO per ora, suddiviso in intervalli da 5 minuti
+
+ Welcome to Cwsim {version} ({date}) by Kevin Schmidt (W9CF) and Gabriele Battaglia (IZ4APU)
+ Benvenuti in Cwsim {version} ({date}) di Kevin Schmidt (W9CF) e Gabriele Battaglia (IZ4APU)
+
+
+ Welcome to Cwsim - Python CW Simulator
+ Benvenuti in Cwsim - Simulatore CW in Python
+
+
+ Authors: Kevin Schmidt (W9CF) and Gabriele Battaglia (IZ4APU)
+ Autori: Kevin Schmidt (W9CF) e Gabriele Battaglia (IZ4APU)
+
+
+ Version: {version}
+ Versione: {version}
+
+
+ Date: {date}
+ Data: {date}
+
Calls miscopied
diff --git a/python/trlineedit.py b/python/trlineedit.py
index 164737b..cd9f26a 100644
--- a/python/trlineedit.py
+++ b/python/trlineedit.py
@@ -14,10 +14,7 @@
#
# See https://www.gnu.org/licenses/ for GPL licensing information.
#
-try:
- from PyQt6 import QtWidgets, QtCore
-except ImportError:
- from PyQt5 import QtWidgets, QtCore
+from PyQt6 import QtWidgets, QtCore
class TrLineEdit(QtWidgets.QLineEdit):
diff --git a/requirements_qt5.txt b/requirements_qt5.txt
deleted file mode 100644
index 2cbe9fa..0000000
--- a/requirements_qt5.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-cffi>=1.15.1
-cycler>=0.11.0
-fonttools>=4.34.4
-kiwisolver>=1.4.4
-matplotlib>=3.5.2
-numpy>=1.23.1
-packaging>=21.3
-Pillow>=9.2.0
-pycparser>=2.21
-pyparsing>=3.0.9
-PyQt5>=5.15.7
-PyQt5-Qt5>=5.15.2
-PyQt5-sip>=12.11.0
-python-dateutil>=2.8.2
-pyxdg>=0.28
-six>=1.16.0
-sounddevice>=0.4.4
diff --git a/shortcuts_keys_comand_it.txt b/shortcuts_keys_comand_it.txt
new file mode 100644
index 0000000..9a76202
--- /dev/null
+++ b/shortcuts_keys_comand_it.txt
@@ -0,0 +1,25 @@
+Riepilogo dei Tasti Rapidi e Comandi per cwsim:
+
+**Tasti Funzione (Invio messaggi CW):**
+* F1 = Invia CQ
+* F2 = Invia Exchange (Rapporto e Progressivo)
+* F3 = Invia TU (Ringraziamento per confermare il QSO)
+* F4 = Invia il tuo nominativo
+* F5 = Invia il nominativo del corrispondente (quello inserito nel campo Call)
+* F6 = Invia QSO B4 (QSO già a log)
+* F7 = Invia il punto interrogativo (?)
+* F8 = Invia NIL (Not in log)
+
+**Azioni del Log e Controllo Simulatore:**
+* Invio (Return) = Passa al campo successivo o registra il QSO a log
+* Esc (Escape) = Ferma la trasmissione CW corrente
+* Alt+W = Pulisce (Wipe) i campi della riga corrente
+* Alt+X = Avvia o Ferma il contest (Start/Stop)
+* Freccia Su / Freccia Giù = Naviga tra i campi / righe del log
+
+**Controlli Audio e Radio:**
+* PgSu / PgGiù (PgUp/PgDown) = Aumenta / Diminuisce la velocità del CW (WPM)
+* Shift+Freccia Su / Shift+Freccia Giù = Regola il RIT (sposta la frequenza di ricezione su/giù)
+* Alt+C = Azzera il RIT (lo riporta al centro)
+* Ctrl+Freccia Su / Ctrl+Freccia Giù = Allarga o restringe la larghezza di banda del filtro audio (Bandwidth)
+* Alt+Freccia Su / Alt+Freccia Giù = Alza o abbassa il Pitch (la tonalità del CW)