Softwarebasteleien
mit PureBasic
Auf der
Suche nach einer nicht zu kryptischen Programmiersprache stieß ich auf PureBasic. Die Liste der Features deckte sich
so ziemlich mit meiner Wunschliste, so dass ich erst gar nicht die Demoversion
ausprobiert habe, sondern gleich mit der Vollversion begann. Und nach jetzt
einjähriger Erfahrung damit kann ich sagen: es hat sich gelohnt.
Endziel der
Bastelei sollte ein Logbuchprogramm sein, das nicht nur die QSO-Daten
abspeichert und auswertet, sondern auch den Stationstransceiver steuert. Dieses
Ziel wurde erreicht, und über den Weg dorthin berichte ich hier. Nicht aus der
Sicht eines Softwareprofis (der ich auch gar nicht bin), sondern aus der eines
engagierten Laien, der nach Überwindung diverser Schwierigkeiten etwas
Vorzeigbares auf die Beine stellt.
Lohnt sich
die Erstellung eines eigenen Logbuchprogramms überhaupt, wo es doch schon so
viele davon gibt? Die Antwort lautet ja, denn das selbst geschriebene ist ein
Unikat, wie es so nicht noch ein Mal existiert.
Man kann
persönliche Vorlieben und technische Erfordernisse einfließen lassen, die es so
bei anderen Programmautoren nicht gibt.
Dieser
Beitrag soll dazu ermuntern, selber mal etwas zu versuchen. Besitzer eines
TS-2000 können das Programm übernehmen, wenn Sie die Angaben wie Rufzeichen und
Namen ändern. Wer ein anderes Gerät sein eigen nennt, muss eben die
Steuerbefehle anpassen. Also ran an den PC, es lohnt sich.
PureBasic
Mit dieser
Programmierumgebung kann man so ziemlich alles machen, was mit einer
Hochsprache möglich ist. So kann z. B. auch Assembler eingebunden werden. Und
es gibt nicht nur eine Windowsversion, sondern auch eine für Linux und eine für
MacOS.
Ich arbeite
mit der Version für Windows und kann deshalb über die anderen Versionen nichts
sagen. Wie ich aber den Foren entnehmen konnte, versuchen die Entwickler, alle
Versionen auf dem gleichen Stand zu halten. Und wer den Kaufpreis ein Mal
bezahlt hat, kann sich alle Versionen und alle Updates herunterladen.
Wo Licht
ist, ist natürlich auch Schatten. So erscheint die Tatsache, dass
Windows-Funktionen wie native PureBasic-Befehle benutzt werden können, im
ersten Moment als ein Bonbon. Es stellt sich aber schnell heraus, dass nur
damit die gewünschte Funktionalität erreicht werden kann. Als Trost sind aber
die erforderlichen Windows-Konstanten gleich mit integriert.
Ein großes
Plus für den Anfänger sind die Foren, die es in deutscher und englischer Sprache gibt. Hier kann
man über die Suchfunktion eine Fülle von Informationen zum eigenen Problem
erhalten, und eigentlich habe ich auf diesem Wege fast alles gelöst. Wenn die
Suche nichts ergibt, kann man natürlich auch Fragen ins Forum stellen und wird
immer eine Antwort bekommen.
Die
Entwickler von PureBasic sind immer am Ball und schalten sich bei grundlegenden
Fragen bzw. Bugs ein.
Weiterhin
gibt es ein Code-Archiv, in dem fertiger Code aus vielen Gebieten darauf
wartet, heruntergeladen zu werden. Und last, but not least gibt es
Userbibliotheken mit compiliertem Code.
Ganz
besonders möchte ich hier PBOSL erwähnen,
"Pure Basic Open Source Library", damit werden viele Gebiete
abgedeckt.
In dem
Logprogramm werden keine Userlibs verwendet, wohl aber Quellcode davon.
Codearchiv und Userlibs sind hier zu
finden.
Die Programme
Alle
erforderlichen Dateien sind in Log.zip enthalten. Diese Datei kann von hier
heruntergeladen werden.
Um den
Einstieg in PureBasic (im Folgenden PB abgekürzt) etwas zu erleichtern, sind in Log.zip einige Übungsprogramme enthalten.
Demo- oder Vollversion
Zur
Ausführung der Programme ist natürlich eine lauffähige PB-Version erforderlich.
Für die
Übungsbeispiele genügt die Demoversion. Die für uns wichtigsten Einschränkungen
der Demoversion sind die fehlende API-Unterstützung sowie die die Beschränkung
auf 800 Zeilen Code.
Die
API-Hürde ist jedoch nicht unüberwindbar. Mit DLL-Zugriffen lässt sie sich
umgehen, und bei den Übungsbeispielen machen wir davon Gebrauch. Das
Logprogramm erfordert allerdings wegen der Zeilenanzahl von über 4000 die
Vollversion.
Installation
Die Installation
folgt den üblichen Regeln. Am einfachsten ist es, wenn man den Vorschlägen
folgt. Dann wird im Programmverzeichnis ein Ordner <PureBasic> angelegt.
Nach dem
ersten Start von PB sind alle Bezeichnungen in Englisch. Zum Umstellen auf
Deutsch das Menü <Files> <Preferencess> öffnen und unter
<Language> Deutsch auswählen. Danach sind alle Menüs und die Hilfe
deutschsprachig.
Für die
Übungs- und Logdateien legen wir im PB-Verzeichnis einen Ordner <Log> an
und kopieren alle extrahierten Dateien aus Log.zip
in diesen Ordner.
Dann legen
wir ebenfalls im PB-Verzeichnis den Ordner <Help> an und verschieben die
Datei PBOSL.chm aus dem Log-Ordner in diesen Ordner.
Windows API
Für
anspruchsvolle Programme wie das Logprogramm ist ein Zugriff auf
Windows-Funktionen unumgänglich. Sehr hilfreich ist es, wenn man dabei auf eine
Dokumentation zurückgreifen kann.
Diese gibt
es auch, sogar kostenlos. Das Platform
SDK von Microsoft ist ein umfassendes Werk, das alle Funktionen, Strukturen
usw. ausführlich beschreibt (in Englisch).
Transceiversteuerung
Alle
Steuerbefehle beziehen sich auf den hier vorhandenen Transceiver TS-2000. Da
jeder Hersteller bei der Steuerung sein eigenes Süppchen kocht, ist eine 1:1
Übernahme für andere Geräte somit nicht möglich. Um die Übersetzung aber etwas
zu erleichtern, sind in Tabelle 1 alle verwendeten Steuerbefehle aufgelistet.
Diese Tabelle befindet sich ebenfalls in Log.zip.
Damit müsste es möglich sein, äquivalente Befehle für andere Geräte zu finden.
Aber nun
genug der Vorrede, jetzt geht die Bastelei los.
Die Übungsprogramme
Buttons
Die Bedienungsoberfläche
sind Fenster, auf denen die Steuerelemente, in PB Gadgets genannt, angeordnet
werden. Als erstes wollen wir uns mit Buttons beschäftigen. Ein Klick auf einen
Button löst ein Ereignis aus, das ausgewertet wird.
Da in
Windows die farbliche Ausgestaltung von Buttons im Normalfall nicht möglich
ist, wurde auf Buttons ganz verzichtet und stattdessen TextGadgets verwendet.
TextGadgets sind statische Elemente, die meistens als Bezeichnungsfelder
(Labels) verwendet werden und nicht auf Benutzereingaben reagieren. Dieses
Verhalten kann aber geändert werden, wie wir später sehen werden.
Um uns den
Quellcode des Übungsprogramms TestButtons.pb anzusehen,
wird diese Datei im Menü <Datei><Öffnen> aus dem PB-Verzeichnis,
Ordner Log,
ausgewählt. Wer mit der Demoversion arbeitet, muss das Semikolon in Zeile 5
entfernen, um aus dem Kommentar ausführbaren Code zu machen. Dadurch wird die
Datei TestAPI.pbi eingebunden, die es uns ermöglicht, auch mit
der Demoversion API-Funktionen zu verwenden.
Danach wird
das Fenster mit OpenWindow() geöffnet. Parameter
sind die Fensternummer, X-Position auf dem Bildschirm, Y-Position auf dem
Bildschirm, Breite, Höhe und Titeltext. Dies sind Pflichtangaben, wobei der
Titel auch leer sein kann (""). Die danach folgenden Parameter sind
optional. In unserem Fall wird das Fenster mit Systemmenuleiste auf dem
Bildschirm zentriert.
Anschließend
wird die Gadgetliste erzeugt und die Gadgets erstellt. Die Pflichtparameter
sind die gleichen wie beim Fenster, nur dass sich die Positionsangaben jetzt
auf das Fenster beziehen.
Die Gadgets
sind wie oben erwähnt TextGadgets als Buttonersatz.
Mit dem
optionalen Parameter #SS_NOTIFY wird Windows veranlasst, bei
Linksklick eine Meldung abzugeben, um dieses Ereignis auszuwerten. #WS_BORDER sorgt für eine einfache Umrandung, #SS_CENTER für
horizontal und #SS_CENTERIMAGE
für vertikal zentrierten Text.
Nach der
Definition der Gadgets werden ihnen für Text und Hintergrund Farben zugewiesen.
Die nach
der Gadgetliste folgende Repeat-Until–Schleife ist
ein zentrales Element jedes PB-Programms mit Bedienoberfläche. Hier werden die
Ereignisse ausgewertet.
Mögliche
Ereignisse sind Klicks auf die Buttons 1 und 2 sowie das Schließen des
Fensters. Die auswertbaren Klicks sind wie bei Buttons nur Linksklicks. Wollen
wir auch den Rechtsklick verwenden, muss in die API-Trickkiste gegriffen
werden. In Zeile 22 wird die Windowsnachricht #WM_RBUTTONUP abgefangen und ausgewertet. Mit ChildWindowFromPoint_() benutzen wir zum ersten Mal ein API-Funktion. Wie wir sehen, wird
an den Funktionsnamen ein Unterstrich angehängt und kann nun wie ein PB-eigener
Befehl verwendet werden. Diese Anweisung gibt aus der Position des Mausklicks
das Handle des betreffenden Gadgets zurück. Mit GetDlgCtrlID_()
wird aus dem Handle die GadgetNr erzeugt, mit der innerhalb von PB auf
das Gadget zugegriffen wird. Mit einer Select-Case-EndSelect-Anweisung
wird je nach zutreffendem Fall eine entsprechende Meldung ausgegeben. Diese
Meldung muss mit "OK" quittiert werden. Beim Klick auf die "Schließen"-Schaltfläche
des Fensters wird das Programm beendet. Bis zu diesem Zeitpunkt wird die Repeat-Until-Schleife ständig durchlaufen.
Gestartet
wird das Programm durch Klick auf das Zahnrad in der Symbolleiste. Vorher ist
sicherzustellen, dass bei den Compileroptionen auch der Debugger aktiviert ist,
der uns bei Fehlern eine entsprechende Meldung anzeigt.
String- und TextGadgets
Unsere
nächste Übungsdatei ist TestStrTxtGadg.pb . Sie beginnt mit der Anweisung EnableExplicit.
Damit wird erzwungen, dass jede Variable mit Define
ausdrücklich deklariert werden muss. Diese Maßnahme verhindert, dass bei
Berechnungen durch falsch geschriebene Variablennamen trotzdem ein (falsches)
Ergebnis zu Stande kommt.
Danach
folgt eine Enumeration aller vorgesehenen Gadgets. Dabei wird jeder
Gadgetkonstanten (kenntlich durch das vorangestellte ' # ') ein Zahlenwert
zugewiesen. Dieser beginnt, wenn nichts anderes bestimmt wurde, bei 0 und hat
eine Schrittweite von 1.
Danach
werden Fenster und Gadgets erstellt. Wir haben wieder ein TextGadget als
Button, zwei TextGadgets als Bezeichnungsfelder und zwei StringGadgets für
Tastatureingaben.
Nach
Programmstart ist der Tastaur-Eingabecursor in keinem der Stringgadgets. Wir müssen
erst auf eines der beiden klicken, um etwas eingeben zu könne. Dieses Verhalten
können wir ändern, wenn wir das Semikolon aus Zeile 25 entfernen. Jetzt wird
beim Starten das Gadget #Str_Upper aktives
Gadget und hat sofort den Eingabecursor.
Die Repeat-Ereignisschleife hat hier als zweite Anweisung
nicht Until, sondern ForEver, um zu verdeutlichen, dass sie
wirklich ständig durchlaufen wird. Wir haben zwei Ereignisarten,
Gadgetereignisse und das CloseWindow-Ereignis. Im letzteren Fall wird das
Programm durch End beendet.
Bei den
Gadgetereignissen müssen wir feststellen, welches Gadget der Verursacher war.
War es ein StringGadget, wird geprüft, ob es ein Change-Ereignis war, d. h.,
dass der Text im Gadget geändert worden ist.
Beim
Buttonklick wird eine entsprechende Meldung ausgegeben.
Wenn wir
"asdfg" in das Eingabefeld 1 eingeben, wird "ASDFG"
angezeigt. Das bewirkt der Parameter #PB_String_UpperCase
bei der Deklarierung. Geben wir den gleichen Text in Eingabefeld 2 ein,
erscheint gar nichts. Das ist auch richtig so, denn mit #PB_String_Numeric haben wir festgelegt, dass nur numerische
Zeichen eingegeben werden können. Mit "12345" können wir das
bestätigen.
Wie wir
gesehen haben, wird die Position der einzelnen Gadgets auf dem Fenster durch
die Angabe von X und Y bestimmt. Zur Platzierung der Gadgets kann der Visual
Designer verwendet werden, der unter <Werkzeuge> aufgerufen werden kann.
Ich habe aber darauf verzichtet.
Nachdem wir
schon Fenster- und Gadgetereignisse kennengelernt haben, fehlt uns noch eine
Ereignisartart: Menüereignisse.
Menüs
Die
Übungsdatei zu diesem Thema ist TestMenüs.pb.
Darin werden sechs TextGadgets als Anzeigeelemente erstellt. Nach der
Gadgetliste wird eine Menüliste mit den entsprechenden Menüeinträgen erzeugt.
In der Ereignisschleife haben wir neben dem CloseWindow-Ereignis
nur Menüereignisse. Bei Klick auf die Menüs wird in einem TextGadget das
jeweilige Menü angezeigt.
Nachdem wir
jetzt schon eine Bedienoberfläche erstellen und anwenden können, wollen wir uns
nun mit dem zentralen Punkt eines Logprogramms beschäftigen: der Datenbank.
Die Datenbank
Da die
QSO-Daten ja dauerhaft erhalten bleiben sollen, benötigen wir eine Datenbank.
Davon gibt es viele, welche soll es denn sein? Beim Stöbern im PB-Forum bin ich
dann auf SQLite gestoßen. SQLite ist kein
Programmungetüm wie Access sondern eine schlanke DLL (Dynamic Link Library),
mit der Datenbanken angelegt und verwaltet werden können.
Irgendwie passen
PureBasic und SQLite gut zusammen. Und das Gute daran ist, dass ab Version 2.0
von PureBasic die vorhandenen Datenbankbefehle auch für SQLite verwendet werden
können. PB ist lediglich mit UseSQLiteDatabase() mitzuteilen,
dass eine SQLite-Datenbank benutzt werden soll.
Dann kann
mit OpenDatabase(#DB, "Datenbank",
"Benutzer", "Passwort") die Datenbank geöffnet werden.
Lesen aus der Datenbank
Datenbanken
bestehen aus Tabellen, in denen Daten verschiedenster Art gespeichert werden.
Die Anzahl der Tabellen hängt vom Verwendungszweck ab. Das Abspeichern von
Daten ist aber kein Selbstzweck. Wer die Daten nie abruft, braucht sie gar
nicht erst abzuspeichern. Erst die Auswertung erfüllt eine Datenbank mit Leben.
Die Mittel für die Auswertung sind Abfragen. Das sind SQL-Anweisungen
(Structured Query Language, Strukturierte Abfragesprache), mit denen aus der
Vielzahl der in den Tabellen gespeicherten Daten eine sinnvolle Auswahl
getroffen werden kann. Sie bestehen aus mindestens einer SELECT-Anweisung.
Diese wird
mit DatabaseQuery(#DB,
"SELECT-Anweisung") an SQLite übergeben. Zum Auslesen der Daten
werden noch FirstDatabaseRow(#DB) oder NextDatabaseRow(#DB) sowie einer der Befehle GetDatabaseDouble(#DB), GetDatabaseFloat(#DB), GetDatabaseLong(#DB),
GetaDatabaseQuad(#DB) oder GetDatabaseString(#DB) benötigt.
Für den
Zugriff auf SQLite-Datenbanken außerhalb des Logprogramms gibt es ein auch ein
sehr gutes Werkzeug: SQLite Expert. Damit kann man besonders leicht SQL-Anweisungen ausprobieren. In der Personal
Edition ist SQLite Expert Freeware und kann von hier heruntergeladen werden.
Für die
folgenden Übungen mit Abfragen wird das Vorhandensein von SQLite Expert
vorausgesetzt.
Die
Datenbank, mit der wir experimentieren wollen, ist Log
DK1IO.db3 und müsste jetzt im
Verzeichnis Log zu finden sein. Nach dem Start von SQLite Expert wird in
<File><Open Database> unsere Datenbank geöffnet. Darin sind mehrere
Tabellen enthalten, unter anderem Tab_Log. Das
ist mein komplettes Log mit mehr als 16000 Einträgen.
Das Log
wollen wir uns anzeigen lassen. Wenn das Kontrollkästchen Limit aktiviert ist, wird es deaktiviert. Unterhalb
der dunkelgrauen Leiste mit der Datenbankbezeichnung den Kartenreiter Data anklicken und auf die Tabelle Tab_Log klicken. Jetzt wird der Tabelleninhalt
angezeigt. Mit den Pfeilbuttons kann darin navigiert werden.
Jetzt
wollen wir unsere erste Abfrage erstellen. Neben Data auf SQL
klicken und im oberen Fenster eingeben
(1) SELECT * FROM Tab_Log
Nun in der
oberen Menüleiste auf <SQL><Execute SQL> klicken. Wir sehen jetzt
die gleiche Anzeige wie vorher. Das Zeichen ' *
' steht als Platzhalter für alle Spalten der Tabelle.
Wollen wir
nur bestimmte Spalten auswählen, sind diese Spalten zwischen SELECT und FROM anzugeben.
Mit
(2) SELECT Datum, UTC, Rufzeichen FROM Tab_Log
werden nur
die angegebenen Spalten angezeigt, allerdings von allen Zeilen. Wollen wir auch
die Anzahl der Zeilen einschränken, müssen wir eine WHERE-Klausel
anfügen:
(3) SELECT Rufzeichen FROM Tab_Log WHERE Rufzeichen BETWEEN
'DA%' AND 'DS%'
Diese
Anweisung zeigt nur deutsche Rufzeichen an. Nein, DS-Rufzeichen werden nicht
angezeigt, die Auswahl ist inklusive DA, aber exklusive DS. ' % ' ist
Platzhalter für kein, ein oder mehrere Zeichen.
Übrigens
sind die SELECT-Anweisungen Strings; wenn
innerhalb davon andere Strings vorkommen, sind diese in einfache
Anführungszeichen zu setzen, wie bei 'DA%' und 'DS%' zu sehen ist.
Nun können
wir also Daten auswählen. Wir wollen aber mehr, nämlich die Daten auch
sortieren. Das geschieht mit ORDER BY.
Die
Anweisung
(4) SELECT * FROM Tab_Log ORDER BY Rufzeichen
zeigt
wieder das komplette Log an, aber nach Rufzeichen sortiert. Mehrfach-QSO mit
einer Station werden auch mehrfach angezeigt. Mit DISTINCT
kann ein Mehrfachauftreten verhindert werden:
(5) SELECT DISTINCT Rufzeichen FROM Tab_Log ORDER BY Rufzeichen
zeigt uns
jedes Rufzeichen nur ein Mal an. Bei mehreren Spalten sieht das aber anders
aus,
(6) SELECT DISTINCT Rufzeichen, Datum, UTC FROM Tab_Log ORDER BY Rufzeichen
wird die
Mehrfachanzeige nicht verhindern, weil sich DISTINCT
auf alle angegebenen Spalten bezieht.
Hier müssen
wir mit GROUP BY arbeiten:
(7) SELECT Rufzeichen, Datum, UTC FROM Tab_Log GROUP BY Rufzeichen
zeigt alle Rufzeichen
nur ein Mal an und sortiert auch gleichzeitig.
Bisher
haben wir unsere Daten nur aus einer Tabelle bezogen. Das ist aber nicht
zwingend erforderlich. Die allgemeine Syntax einer Abfrage lautet
SELECT Ergebnis FROM Quelle [WHERE] [GROUP BY] [ORDER BY]
Die
Schlüsselwörter in Klammern sind optional.
Quelle kann eine oder mehrere Tabellen,
eine oder mehrere Abfragen oder eine Kombination daraus sein.
Wir wollen
nun eine Abfrage konstruieren, bei der Quelle
eine andere Abfrage ist. Mit
(8) SELECT * FROM (SELECT Datum, UTC, Rufzeichen FROM Tab_Log)
erhalten
wir das gleiche Ergebnis wie mit Abfrage (2). Es werden aber nur drei Spalten
angezeigt, obwohl wir mit dem Zeichen ' * ' als
Platzhalter für alle Spalten operiert haben. Das kann auch gar nicht anders
sein, denn Ergebnis kann nicht mehr Spalten
aufweisen als Quelle liefert.
Seit
Februar 2007 bin ich Mitglied im EPC (European PSK Club). Dort gibt es ein
attraktives Diplomprogramm. Die Diplomauswertung hatte ich anfangs in das
Logprogramm integriert. Dann hat aber DK5UR, ein Manager des EPC, mit
UltimateEPC ein Auswerteprogramm herausgebracht, das keine Wünsche mehr offen
lässt. Aber eine Aufgabe blieb mir noch: UltimateEPC erwartet als Input eine
ADIF-Logdatei.
Die Abfrage
zur Umsetzung der Logtabelle in eine ADIF-Datei wollen wir uns etwas näher
ansehen. Sie hat eine Besonderheit: hier werden zwei Abfragen miteinander
verknüpft.
Grundsätzlich
gibt es zwei Möglichkeiten, Abfragen miteinander zu verknüpfen. Hier werden mit
UNION die Ergebniszeilen der zweiten Abfrage an
die der ersten angefügt. Voraussetzung dafür ist, dass die Anzahl der
Ergebnisspalten in beiden Abfragen gleich ist. Die Spaltennamen können
unterschiedlich sein.
(9) SELECT Datum, UTC, Rufzeichen,
Band, Mode,
Loc, IOTA
FROM Tab_Log WHERE Mode LIKE '%PSK%'
SELECT Datum, UTC, SWL, Band, Mode, '', '' FROM Tab_Log WHERE Mode LIKE '%PSK%' AND SWL > ''
In der
ersten Abfrage sehen wir, dass die für Diplome relevanten Spalten aus der Logtabelle
ausgewählt werden. Die WHERE-Klausel sorgt dafür, dass nur PSK-QSO übernommen
werden, unabhängig davon, ob BPSK31 oder QPSK63.
Die zweite
Abfrage hat etwas mit den EPC-Diplomregeln zu tun. Die sagen nämlich aus, dass
Empfangsberichte von EPC-Mitgliedern geloggten QSO gleichgestellt sind. In der
Logtabelle gibt es eine Spalte SWL, in die bei Eintreffen eines
Empfangsberichts das SWL-Rufzeichen eingetragen wird.
In der
Abfrage werden nun die Logzeilen ausgewählt, in denen SWL
größer ist als eine leere Zeichenkette (''). Diese Zeilen werden nun durch UNION an die Zeilen der ersten Abfrage angehängt.
Die andere
Möglichkeit zum Verknüpfen von Abfragen ist die mittels einer JOIN-Anweisung. Dabei werden nicht Zeilen angefügt,
sondern an vorhandene Zeilen neue Ergebnisspalten. Voraussetzung dafür ist,
dass die zu verknüpfenden Abfragen über je eine gleichartige Ergebnisspalte
verfügen. Zur Verdeutlichung dieses Prinzips wollen wir uns mit einer recht
komplexen Abfrage beschäftigen. Es handelt sich dabei um eine Abfrage zur
Auswertung des DARC-Weihnachtswettbewerbs, in diesem Falle den von 2006.
1
SELECT E.UTC AS UTC, E.Rufzeichen
AS Rufzeichen,
E.Band AS Band, RSTr, DOK, QTH, Mult, Mult2, Dupe FROM
2
(((SELECT
UTC, Rufzeichen,
Band, RSTr,
DiplKz AS DOK, QTH FROM Tab_Log WHERE (Datum =
'2006-12-26' AND UTC
BETWEEN '0830' AND
'1100')) AS A
3 LEFT JOIN
4 (SELECT * FROM (SELECT UTC, Band, DiplKz AS Mult FROM Tab_Log WHERE (Datum = '2006-12-26' AND
UTC BETWEEN
'0830' AND '1100') ORDER
BY UTC DESC)
GROUP BY Mult,
Band) AS B
5 ON A.UTC = B.UTC) AS C
6 LEFT JOIN
7 (SELECT * FROM (SELECT UTC, CASE WHEN SUBSTR(Rufzeichen, 3, 1) BETWEEN
'0' AND '9' THEN SUBSTR(Rufzeichen, 1, 3) WHEN
SUBSTR(Rufzeichen,
3, 1) BETWEEN 'A' AND
'Z' THEN SUBSTR(Rufzeichen, 1, 2) WHEN
SUBSTR(Rufzeichen,
3, 1) = '/' THEN CASE WHEN SUBSTR(Rufzeichen,
2, 1) BETWEEN '0' AND
'9' THEN SUBSTR(Rufzeichen,
1, 2) ELSE SUBSTR(Rufzeichen,
1, 2) || '0' END END AS Mult2,
Band FROM Tab_Log WHERE (Datum = '2006-12-26' AND
UTC BETWEEN
'0830' AND '1059') ORDER
BY UTC DESC)
GROUP BY Mult2,
Band) AS D
8 ON C.UTC = D.UTC) AS E
9 LEFT JOIN
10 (SELECT *, CASE WHEN COUNT(*) THEN NULL ELSE 1 END AS Dupe FROM (SELECT UTC, Rufzeichen,
Band FROM Tab_Log WHERE (Datum = '2006-12-26' AND
UTC BETWEEN
'0830' AND '1100') ORDER
BY UTC DESC)
GROUP BY Rufzeichen,
Band HAVING COUNT(*)) AS F
11 ON E.UTC = F.UTC ORDER BY UTC
Die Abfrage
sieht gewaltig aus, aber wir zerlegen sie in ihre Einzelteile, dann ist alles
überschaubar.
Beginnen
wir mit Zeile 2. Diese Unterabfrage wählt alle relevanten Daten aus der
Contestzeit aus und erhält den Aliasnamen A. LEFT
JOIN in Zeile 3 wählt alle Zeilen aus A
aus und verknüpft sie mit der Unterabfrage in Zeile 4. A
ist sozusagen die Basis der Gesamtabfrage.
Zeile 5
besteht aus zwei SELECT-Anweisungen; das hat
folgenden Grund: Mit dieser Abfrage wollen wir den Multiplikator Mult bestimmen. Das sind die unterschiedlichen DOK
pro Band. Mit der äußeren SELECT-Anweisung SELECT * FROM (…) GROUP BY Mult, Band wird für jeden DOK pro Band eine Ergebniszeile
gebildet. Ist ein DOK auf einem Band mehrfach vertreten, wird die Zeile des
letzten Auftretens als Ergebniszeile herangezogen. Üblicherweise wird bei der
Logauswertung jedoch das erste Auftreten gewertet. Um beides unter einen Hut zu
bringen, wenden wir einen Trick an: die Logzeilen, die die äußere SELECT-Anweisung gruppiert, werden vorher von der
inneren SELECT-Anweisung in eine zeitlich
umgekehrte Reihenfolge gebracht (ORDER BY UTC DESC). Dadurch
wird die Zeile zur Ergebniszeile, in der der DOK zeitlich gesehen zum ersten
Mal auftritt. Die Abfrage in Zeile 4 erhält den Aliasnamen B.
A und B
werden in Zeile 5 durch ON A.UTC = B.UTC
miteinander verknüpft. Diese Verknüpfung bewirkt, dass in jeder
Ergebniszeile der Abfrage A nach den eigenen
Spalten die Spalten der Abfrage B angefügt
werden.
Das
Ergebnis der Verknüpfung von A und B wird als C
bezeichnet und dient als Basis für den nächsten Schritt; den Multiplikator
Mult2 zu ermitteln. Das sind die unterschiedlichen Präfixe pro Band. Da die
Präfixe nicht in Reinform vorliegen, müssen sie aus den Rufzeichen extrahiert
werden. Das ist der Grund für die Länge der Unterabfrage in Zeile 7, die den
Aliasnamen D erhält. Wie im vorigen Fall wird auch hier aus den genannten
Gründen die zeitliche Reihenfolge umgekehrt. In Zeile 8 werden C und D wieder
über UTC miteinander verknüpft und das
Ergebnis E genannt.
Im Letzten
Schritt werden in Zeile 10 Mehrfach-QSO (Dupes) ermittelt. Hier wird die
Gruppenbildung mit Rufzeichen, Band durchgeführt. Die Ergebnisspalte Dupe enthält die Anzahl der Dupes pro Gruppe (d.
h. pro Rufzeichen und Band). Als Besonderheit haben wir noch eine HAVING- Klausel. HAVING
kann mit WHERE gleichgesetzt werden. Der
Unterschied liegt darin, wann die Anweisungen ausgeführt werden. WHERE wird vor der Gruppierung (GROUP BY) ausgeführt, HAVING
danach. Hier wird nur dann ein Ergebnis geliefert, wenn COUNT(*) > 0 ist. Die Abfrage
in Zeile 10 wird mit F bezeichnet.
Anschließend
werden in Zeile 11 E und F über UTC
miteinander verknüpft und das Ergebnis wieder in die ursprüngliche zeitliche
Reihenfolge gebracht.
Bleibt nur
noch die bisher unterschlagene Zeile 1. Hier werden die Ergebnisspalten
ausgewählt, denn die in den Unterabfragen mehrfach auftretenden Spalten sind im
Endergebnis natürlich auch enthalten. Und noch etwas ist zu berücksichtigen:
die Verknüpfung über UTC kann nur dann ein
korrektes Ergebnis liefern, wenn für jede Zeitangabe nur ein QSO im Log steht.
Trifft das nicht zu, ist z. B. mit dem Rufzeichen in der ON-Anweisung eine weitere Spezifizierung vorzunehmen: ON (A.UTC = B.UTC AND A.Rufzeichen = B.Rufzeichen).
Dann muss Rufzeichen in B natürlich auch als Ergebnisspalte ausgewählt
werden.
Bisher haben
wir uns ausschließlich mit Lesezugriffen auf die Datenbank beschäftigt. Aber
spätestens beim Abspeichern der QSO-Daten müssen wir auch schreibend auf die
Datenbank zugreifen.
Schreiben in die Datenbank
Für alle
Nichtleseoperationen stellt PB die Anweisung
DatabaseUpdate(#DB, "SQL-Anweisung") zur Verfügung.
Am Ende eines QSO werden die festgelegten
Daten in die Tabelle Tab_Log übernommen. Dabei wird ein kompletter
Datensatz in die Tabelle geschrieben. Die Syntax dafür lautet
INSERT INTO Tabellenname [(Spaltenliste)]
VALUES(Werteliste)
Die eckigen
Klammern besagen, dass die Angabe innerhalb der Klammern weggelassen werden
kann. Wird aber eine Spaltenliste angegeben, muss die Anzahl der Spalten mit
der Anzahl der Werte in der Werteliste übereinstimmen. Der neue Datensatz wird
an das Ende der Tabelle geschrieben.
Aber auch,
wenn z. B. QSO-Daten später geändert werden sollen, müssen wir in die Tabelle
schreiben. Für diesen Fall benutzten wir eine UPDATE-Anweisung:
UPDATE
Tabellenname SET
Spaltenname = Wert [WHERE]
Soll der
Wert nicht in alle Zeilen der Tabelle geschrieben werden, muss mit der WHERE-Klausel eine Einschränkung vorgenommen werden,
z. B. mit
WHERE Rufzeichen =
'XX0YY'.
Eine
besondere Art des Schreibzugriffes erfolgt mit CREATE
TABLE. Dabei wird eine neue
Tabelle angelegt.
Wir sind
gut beraten, wenn wir vor Experimenten mit Schreibzugriffen von der Tabelle
eine Kopie anfertigen.
Mit
CREATE TABLE Tab_Log1 AS SELECT * FROM Tab_Log
erstellen wir
eine Kopie der Tabelle Tab_Log mit dem Namen Tab_Log1.
Gelegentlich
kommt es vor, dass wir Daten nicht in eine Tabelle schreiben wollen, sondern
Daten daraus entfernen müssen.
Löschen aus der Datenbank
Zum Löschen
von Daten bedienen wir uns der DELETE-Anweisung.
Mit
DELETE FROM
Tabellenname
werden alle
Einträge aus der Tabelle gelöscht. Um nur bestimmte Zeilen zu löschen, wird
eine WHERE-Klausel angefügt, wie in folgendem
Befehl:
DELETE
FROM Tab_Log WHERE Rufzeichen = 'XX0YY'
Um eine
Tabelle aus der Datenbank zu entfernen, wird folgende Anweisung benutzt:
DROP TABLE
Tabellenname
Nachdem wir
nun Datenbankexperten geworden sind, wollen wir uns mit der Anzeige der mit
Hilfe von Abfragen gewonnenen Daten befassen. Wir wollen ja nicht jedes Mal das
Logprogramm verlassen und mit Hilfe von SQLite Expert eine aufwändige Abfrage
erstellen. Nein, das soll nach Klick auf einen Menüpunkt programmgesteuert ablaufen.
Das ListIconGadget
Zur Anzeige
von Datenbankinhalten steht uns in PB das ListIconGadget zur Verfügung. Die
Anzeige geschieht dabei wie in einer Exceltabelle. Es besteht aber ein
gravierender Unterschied zwischen beiden: während man in einem
Exceltabellenblatt den Tastatur-Eingabecursor in eine beliebige Zelle setzen
und den Text dieser Zelle editieren kann, ist das beim ListIconGadget nicht
möglich. Wie wir diese Einschränkung umgehen können, sehen wir später.
Zu diesem
Gadget gibt es wieder eine Übungsdatei namens TestListIconGadget.pb. Dieses Programm füllt das Gadget mit
dem vollständigen Log und zeigt es an.
Betrachten
wir den Quellcode dieser Datei näher.
Benutzer
der PB-Demoversion müssen wieder das Semikolon in Zeile 7 entfernen. Die
Anweisung danach bindet die Datei SQLite3.pbi in das Programm ein, denn wir wollen ja auf
die Datenbank zugreifen. In den Zeile 14 bis 16 werden Variable als global
deklariert, damit auch innerhalb von Prozeduren auf sie zugegriffen werden
kann. RS ist eine Variable, der eine Struktur
aus SQLite3 zugewiesen wird. *RS ist der Zeiger
auf diese Variable. Ab Zeile 53 werden das Fenster und die beiden Gadgets (#LI_Log und #Str_Edit)
eingerichtet.
In Zeile 66
wird auf die Callbackfunktion in Zeile 23 verwiesen. Die Callbackfunktion steht
in engem Zusammenhang mit den API-Funktionen. Hier werden die benötigten
Meldungen und Ereignisse ausgewertet, die PB nicht zur Verfügung stellt.
Danach wird
die SQLite-DLL initialisiert und die Datenbank geöffnet. Dann wird der
Spaltenaufbau für das Gadget organisiert. In unserer Datenbank existiert eine
Tabelle Tab_AddGadgetColumn, in der der
Spaltenaufbau für alle vorkommenden Zwecke gespeichert ist, natürlich auch für
das Log. In einer For-Next-Schleife werden die
Spalteninformationen (Titel und Breite) auf das Gadget übertragen. Handelt es
sich um boolesche Spalten (Neu, QSLe, QSLa, DL0NRU, QSLPrt),
werden sie zentriert.
In Zeile 94
steht die SELECT-Anweisung zur Datenauswahl.
Dieser String wird als Parameter an die Anweisung DatabaseQuery()
übergeben. Ein weiterer Parameter ist die Datenbank-Nr. #DB.
In zwei
verschachtelten For-Next-Schleifen
wird nun das Gadget mit Daten gefüllt. In Zeile 106 haben wir die Besonderheit,
dass die Daten ausgetauscht werden, wenn es sich um "1" oder
"0" handelt. Dann werden die Zeichen mit ASCII-Code $53 bzw. $A3
eingesetzt. Das Zeichen "$"
steht für hexadezimale Zahlen.
$53 und $A3 sind im Fontsatz
'Wingdings 2', den wir in Zeile 21 definiert haben, die Zeichen 'S' und '£' als ausgefülltes und leeres
Kontrollkästchen. Um diese Zeichen auch wirklich anzuzeigen, wird in der schon
erwähnten Callbackfunktion ab Zeile 32 die Windowsmeldung #NM_CUSTOMDRAW
ausgewertet.
Für die booleschen Spalten wird der
Fontsatz 'Wingdings 2' ausgewählt, für alle anderen Spalten 'Microsoft Sans
Serif'.
Wenn wir das Programm starten,
passiert erst einmal gar nichts, jedenfalls nichts sichtbares. Das ist die
Zeit, die zum Füllen des ListIconGadgets benötigt wird. Die fast 16500
Datensätze können eben nicht in Nullkommanichts übertragen werden. Aber
irgendwann ist es soweit, das Log wird angezeigt.
Eine Anzeige ist ja gut und schön,
aber irgendwann tritt ja auch mal die Notwendigkeit auf, Daten zu ändern. Wie
anfangs erwähnt, ist das von Windows nicht vorgesehen. Aber mit einem Trick schaffen
wir es trotzdem.
Wenn wir auf die Zelle klicken,
deren Inhalt wir ändern wollen, wird in der Callbackfunktion ab Zeile 28 die
Zeilen- und Spalteninformation der betreffenden Zelle übernommen. In der
Ereignisschleife werten wir das Ereignis #PB_EventType_LeftClick ab Zeile 126 aus. Als erstes wird der
Zellinhalt und Spaltentitel ausgelesen und gespeichert. Dann wird die QSONr (ID) aus Spalte 1 ausgelesen und gespeichert.
Abhängig vom Spaltentitel geht es
weiter.
Handelt es sich um eine boolesche
Spalte, wird für die Anzeige das leere Kontrollkästchen gegen ein ausgefülltes
getauscht bzw. umgekehrt. In die Datenbank wird "1" oder
"0" geschrieben. Dafür wird die UPDATE-Anweisung
benutzt, die als Stringparameter an die Funktion SQLite3_Execute() übergeben wird.
Handelt es sich um eine Spalte mit
Textinhalt, wird der Zellinhalt in das noch unsichtbare StringGadget
geschrieben. Dann werden Abmessungen und Position des Zellrechtecks auf das
StringGadget übertragen und dieses sichtbar gemacht. Was wir jetzt sehen ist
nicht mehr die Zelle, sondern das darüberliegende Stringgadget. Dieses erhält
den Tastaturcursor und kann nun editiert werden.
Ändern wir tatsächlich den Text,
wird, wird für das StringGadget das Change-Ereignis ausgelöst (Zeile 151).
Dort wird aber nichts weiter
gemacht, als die Tatsache der Änderung abzuspeichern.
Verlassen wir das StringGadget,
indem wir auf den Spaltenkopf des ListIconGadgets klicken, wird das
LostFocus-Ereignis auftreten (Zeile 153). Wenn der Text im StringGadget
geändert worden ist, wird der Text ausgelesen und mit der UPDATE-Anweisung in die Datenbanktabelle geschrieben.
Selbstverständlich wird der neue Text auch in die Zelle des ListIconGadgets
geschrieben. Dann wird das StringGadget wieder unsichtbar gemacht und wir sehen
wieder die ursprüngliche Zelle, aber mit geändertem Text. Durch die
beschriebene Manipulation erscheint es tatsächlich so, als ob das
ListIconGadget direkt editierbar ist.
Nach der intensiven Beschäftigung
mit Datenbankthemen wollen wir uns jetzt einem Thema zuwenden, das zwar nicht
direkt mit dem Logbuch zusammenhängt, wohl aber mit der Transceiversteuerung.
Die serielle Schnittstelle
Diese Hardwarekomponente ist zwar in
immer weniger Computern anzutreffen, wird aber zur Kommunikation mit dem
Transceiver benötigt. Wer einen Computer ohne serielle Schnittstelle besitzt,
muss nun keineswegs auf die PC-Steuerung verzichten. Als Ersatz stehen uns
USB/Seriell-Wandler zur Verfügung. Bei mir verrichten zwei dieser Konverter
ihren Dienst, ohne dass sich negative Abweichungen vom Original ergeben hätten.
Auch für den Zugriff auf die
serielle Schnittstelle hat PB Version 4.20 Verbesserungen gebracht. Jetzt sind
auch für diesen Zweck PB-Befehle verfügbar, so dass nicht mehr auf die vorher
benutzte Include-Datei zurückgegriffen werden muss.
Wir
verwenden 4 Befehle:
OpenSerialPort(…)
CloseSerialPort(…)
ReadSerialPortData(…)
WriteSerialPortString(…)
Vom
CommPort lesen
Vom CommPort wird nur dann gelesen,
wenn der Transceiver vorher zur Abgabe eines Statusberichts aufgefordert wurde.
Lesezugriffe laufen über die
Prozedur CommIn() am Anfang von LogGenProg.pbi. Rückgabewert der Prozedur sind die
Empfangsdaten.
Zum
CommPort schreiben
Es gibt zwei verschiedene Arten von
Steuerbefehlen, die an den Transceiver gesendet werden:
1. Befehle zur Abgabe eines
Statusberichts
2. Befehle zur Statusänderung
"PC;"
z. B. fordert den TS2000 auf, die eingestellte Ausgangsleistung mitzuteilen. Eine
mögliche Antwort ist "PC025;"
Der Befehl "PC100;" stellt die Ausgangsleistung auf 100W ein.
Schreibzugriffe erfolgen über die
Prozedur CommOut() in LogGenProc.pbi. Als Parameter wird der zu sendende
String übergeben und das für die Syntax erforderliche Semikolon angehängt.
Die folgende Verzögerung soll
verhindern, dass ein eventuell folgender Lesezugriff für den Transceiver zu schnell erfolgt. Mit der
Verzögerung kann zu kürzeren Zeiten hin experimentiert werden.
Die Prozedur hat keinen
Rückgabewert.
CW/Tune
Zur Tastung
des Senders wird die API-Funktion EscapeCommFunction_()
verwendet. Parameter sind das CommHandle
und der gewünschte Zustand der DTR-Leitung (#SETDTR
oder #CLRDTR).
Bei Erfolg
der Aktion wird ein Wert <> 0 zurückgegeben.
Mit Hilfe
der Schnittstellenbefehle können wir jetzt eine erste Anwendung
zusammenbasteln.
Das erste
Anwendungsprogramm
Häufig sind
es die kleinen Helfer, die einem das Leben erleichtern. Hier nun macht ein
Programm nichts anderes, als den Transceiver auf den Betrieb eines anderen
Programms vorzubereiten.
Ich schaue
ab und zu nämlich auch mal bei APRS hinein, und dafür sind etliche Einstellungen
am Transceiver erforderlich. Dieses kleine Hilfsprogramm übernimmt das für mich,
vom Einschalten des Transceivers über den Aufruf von UIView bis zum Ausschalten
nach Beendigung.
Das
Programm benötigt keine Benutzeroberfläche. Die Quellcodedatei dafür ist TS2K UIV-Init VHF.pb.
Für die
Benutzer der Demoversion das übliche Spiel zum Einbinden von TestAPi.pbi.
Zuerst wird
mit OpenSerialPort() die Schnittstelle geöffnet.
Dann senden wir die ersten Befehle an den CommPort, wobei der erste Befehl
"U" gar kein Befehl ist, sondern ein Dummy, um an der Schnittstelle
Polaritätswechsel zu erzeugen und sie damit zum Leben zu erwecken. Der folgende
Befehl "PS1;" ist aber echt, er schaltet den Transceiver
ein. Die folgende Verzögerung von 1s benötigt der TS-2000 zum Aufrüsten Die
dann folgenden Anweisungen nehmen die Einstellungen für Packetbetrieb vor.
In RunProgram() muss der vollständige Pfad für das
auszuführende Programm angegeben werden.
Danach wird
die Schnittstelle geschlossen und das APRS-Programm UIView aufgerufen. Dieses
übernimmt jetzt die Kontrolle über die Schnittstelle.
Wenn UIView
beendet wird, übernimmt unser kleines Programm wieder und schaltet den TS-2000
aus.
Wer hier
selbst Hand anlegen will, kann Erweiterungen vornehmen, z. B. den Zustand
vorher abfragen und speichern und anschließend wieder auf diesen Zustand
zurücksetzen.
Wie wir
sehen, ist das nichts Großartiges, aber doch Nützliches.
Übrigens,
wenn wir ein Programm gestestet und für gut befunden haben, müssen wir das
nicht immer in der PB-Entwicklungsumgebung laufen lassen. Mit Menüpunkt
<Compiler><Executable erstellen> wird eine EXE-Datei erzeugt, die
dann für sich lauffähig ist.
Das große Projekt –
ein Logbuchprogramm
Vom
Leichten zum Schweren, vom Kleinen zum Großen, diesem Methodikkonzept folgend
wollen wir uns jetzt an die große Aufgabe wagen. Das Große bezieht sich aber
wirklich nur auf den Umfang der Aufgabe, denn eigentlich ist es zum größten
Teil eine Anhäufung von kleinen und unkomplizierten Programmteilen.
Wo es
tatsächlich schwierig wäre, nämlich bei PSK, da gibt es glücklicherweise Hilfe
in Form einer DLL, die alle Rechenarbeit sowie die Verwaltung der Soundkarte
übernimmt. Dazu aber später mehr.
Bezüglich
der Steuerung hat wohl jeder seine eigene Philosophie, welche Gerätefunktionen
vom PC aus steuerbar sein sollen. Für mich bestand nie ein Zweifel daran, die
Frequenzeinstellung mit dem Abstimmknopf am Funkgerät vorzunehmen. Aber so
ziemlich alles andere, was man im Betrieb alles ändern muss/kann, wird vom PC
per Mausklick erledigt.
Hier eine
Übersicht über die vom Programm gesteuerten Funktionen:
CW-Bandbreite
CW-Geschwindigkeit
Ausgabe von Speichertexten in CW und
PSK
Einstellung der
DSP-Rauschverminderung
Ausgansleistung
Betriebsart
AGC ein/aus
Split ein/aus
Sender zum Abstimmen einschalten
(Tune)
Aber wer
will, kann hier aus dem Vollen schöpfen, ich habe beim TS-2000 noch keine
Funktion entdeckt, die sich nicht auch vom PC aus steuern lassen könnte.
Wer
Diplomsammler ist, für den ist ein eigenes Logprogramm ein Eldorado.
Wie schon
erwähnt, bin ich EPC-Mitglied und habe nach Eintritt sofort Hand angelegt, um
die Diplomauswertung anzupassen.
Wenn nun
ein Rufzeichen im PSK-Empfangsfenster erscheint und ich es mit einem Mausklick
in das entsprechende Feld übernehme, wird das Rufzeichen rot angezeigt, wenn
sich dahinter eine neue EU-Area verbirgt, und grün wird es, wenn das ein neuer
Prefix ist. Dann heißt es: dranbleiben.
Also, noch
ein Mal, es lohnt sich auf jeden Fall, hier tätig zu werden.
Also gibt
es jetzt nur eins: ran an den PC.
Während wir
bei unserem kleinen Hilfsprogramm gar keine sichtbare Bedienoberfläche hatten,
ist diese beim Logprogramm unbedingt notwendig. Darauf werden die Eingabefelder
für die QSO-Daten, die Schaltflächen für bestimmte Befehle oder einfach nur die
Bezeichnungen für die Eingabefelder angeordnet.
Zusätzlich
zu den Gadgets, die wir bisher kennengelernt haben, verwenden wir noch zwei
Gadgettypen: das ImageGadget und das EditorGadget. Mit dem ImageGadget können
Images (Bilder) dargestellt werden. Bei uns kommt das bei PSK zum Anzeigen des
Wasserfalls zum Einsatz. Das EditorGadget dient ebenfalls in PSK als Empfangs-
und Sendetextfenster.
Die
Programmausführung beginnt am Anfang der Datei LogMain.pb. Als erstes werden 4
Include-Dateien eingebunden. Die Aufteilung auf 5 Dateien ist nicht zwingend
erforderlich und wurde nur aus Gründen der Übersicht vorgenommen.
In LogConstGlob.pbi sind Konstanten und globale
Variablen definiert. Konstanten beginnen mit einem "#" und können
numerische oder Textzeichen enthalten.
LogWndBetr.pbi enthält die Programmteile zum
Erstellen der Windows, Gadgets und Menüs für die Betriebsarten CW, FM und SSB.
Dort werden auch gleich die Farbeinstellungen für die einzelnen Elemente vorgenommen. Hier werden auch bestimmten
Tasten mit AddKeyboardShortcut() Ereignisse
zugeordnet, die wie Menüereignisse verarbeitet werden.
LogGenProc.pbi enthält viele Prozeduren
(Funktionen) für verschiedene Zwecke.
In LogPSKProc.pbi
werden die Funktionen für die Betriebsart PSK aufgerufen.
Nach dem
Einbinden werden die Fenster erstellt, erst Window_Log
und dann Window_Betr. Window_Log
wird nur vorbereitend geöffnet, ist aber nicht sichtbar, weil der Parameter #PB_Window_Invisible verwendet wird. Dieses Fenster
dient zur Anzeige von Logdaten als Log oder als Diplomauswertung. Window_Betr ist das Betriebsfenster für alle
Betriebsarten.
Nach dem
Öffnen des Fensters wird die Gadgetliste erstellt.
In der
Gadgetliste sind alle Gadgets aufgeführt, die wir für die Betriebsarten CW, FM
und SSB benötigen. Da sind im Wesentlichen die Eingabefelder für die QSO-Daten.
Bild 1 zeigt das Fenster für die Betriebsart CW.

Bild1
Ganz oben sehen
wir die Leiste mit dem Fenstertitel und den Systemtools. Darunter ist die
Menüleiste mit den Menütiteln. Im eigentlichen Fenster sehen wir rechts den
Block "Log" mit den Eingabe- und Anzeigefeldern. Die gelben Felder
enthalten die Daten, die am Schluss abgespeichert werden. Die Felder Band, Mode, Leistung und QSO Nr.
werden programmgesteuert ausgefüllt und können nicht per Tastatureingabe
geändert werden. Die anderen gelben
Felder erwarten Eingaben, wobei in den drei Rufzeichenfeldern Rufzeichen, Via und And. Rufz.
Großbuchstaben angezeigt werden. In die hellgrauen umrandeten Anzeigefelder
schreibt das Programm die entsprechenden Werte.
Nach
Eingabe des Rufzeichens und Verlassen des Feldes per Tab oder Mausklick wird in
dem hellgrauen Feld Land der zu dem Rufzeichen
gehörende Ländername angezeigt.
Bei Eingabe
eines Locators im richtigen Format erscheinen in den hellgrauen Feldern daneben
Entfernung und Richtung.
Die
hellgrauen Felder dienen nur der Information, die darin enthaltenen Daten
werden am QSO-Ende nicht gespreichert.
Mit den
Pfeilbuttons neben dem Feld "Leistung'
wird die Ausgangsleistung in den Stufen 5W, 10W, 25W, 50W und 100W
eingestellt. Das Kontrollkästchen neben "QSL" wird gecheckt, wenn
eine QSL-Karte rausgehen soll.
Mit Klick
auf den Button "Log" unterhalb des Lograhmens werden die QSO-Daten
abgespeichert.
Zusätzlich
zu den sichtbaren Daten werden noch Datum und Uhrzeit (UTC) ins Log übernommen.
Die
PC-Systemzeit ist übrigens UTC und kann mit der API-Funktion GetSystemTime() abgefragt werden.
Der
komplette Logvorgang ist als Gadgetereignis unter Case
#Button_Log in der Ereignisschleife in LogMain
untergebracht.
Unter dem
Log-Button befindet sich der Tune-Button, mit dem der Sender zum Abstimmen
eingeschaltet werden kann. Der Button wird dann rot. Ein weiterer Klick
schaltet den Sender wieder aus und der Button übernimmt wieder seine
Normalfarbe.
Rechts
davon sind die Buttons für verschieden Speichertexte angeordnet. Der am
häufigsten benutzte ist der für CQ-Rufe; aber auch eigene Vorstellung und die
Stationsbeschreibung können als Morsetext ausgegeben werden.
Die
Prozedur, die das bewerkstelligt, ist Morseausg().
In der äußeren von zwei verschachtelten For-Next-Schleifen
wird der als Parameter übergebene Speichertext Zeichen für Zeichen mit Hilfe
der Tabelle Tab_Morsezeichen in Morsecode
umgesetzt und der Zeichenabstand hinzugefügt.
In der
inneren Schleife wird die Zeichenkette des Morsecodes (bestehend aus "0"
und "1") Element für Element in den Wert umgewandelt und der Wert zum
Setzen der DTR-Leitung benutzt. Diese Leitung tastet den Sender.
Mit Delay() wird der jeweilige Zustand für 1 Elementlänge
(= Punktlänge) beibehalten.
Die
Genauigkeit von Delay() ist für diesen Zweck
völlig ausreichend; unterschiedliche Punktlängen konnten nicht festgestellt
werden.
Ein
erneuter Klick auf den entsprechenden Button bricht die Morseausgabe ab.
Die
Geschwindigkeit der Speichertext-Morseausgabe und die des Keyers im TS-2000
werden mit den Pfeilbuttons links oben im Block CW-Speed
mit einer Schrittweite von 1 WpM eingestellt. Der einstellbare Bereich reicht
von 10 WpM bis 30 WpM.
Darunter
haben wir den Block für die Einstellung der CW-Filterbreite.
Beim Einschalten wird eine Breite von 400 Hz eingestellt. Die Beschriftung des
aktiven Buttons ist rot.
Es folgt
die Rauschminderung (Noise Reduction). Sie ist
in Grundstellung ausgeschaltet, die Buttons für den Grad der Rauschminderung
sind gesperrt. Bei Klick auf NR1 oder NR2 werden die Pfeilbuttons entsperrt und
der NR-Level kann von 0 bis 9 eingestellt werden.
Im Rahmen Split haben wir den entsprechenden Button, der im
Normalbetrieb auf "Aus" steht. Bei Klick darauf wird die
"XIT" des Transceivers eingeschaltet, die Einstellung der Ablage wird
am Gerät vorgenommen. Der folgende Button im Rahmen TF-Set
arbeitet mit Split zusammen. Ist Split auf "Aus", ist der Button TF-Set gesperrt. Ist Split
auf "Ein", bewirkt ein Klick auf TF-Set,
dass zusätzlich die "RIT" eingeschaltet und die Bandbreite auf 2000
Hz erhöht wird. Damit soll erreicht werden, dass man (hoffentlich) die
Gegenstation der DX-Station hört und sich auf diese Frequenz setzt.
Der letzte
Button auf unserem Fenster ist der für die AGC.
Im CW-Betrieb ist die AGC ausgeschaltet und kann durch Klicken auf den Button
eingeschaltet werden.
Zwei Felder
sind bisher unsichtbar: DiplKz und Lfd. Nr.
DiplKz steht für Diplomkennzeichen und hat
für die verschiedenen Diplome verschiedene Bezeichnungen. Wird ein deutsches
Rufzeichen eingegeben, erscheint das Eingabefeld mit der Bezeichnung
"DOK". Bei britischen Calls ist die Bezeichnung "WAB", bei
russischen "RDA" usw.
Das Feld Lfd. Nr. erscheint nur, wenn es im entsprechenden
Menü ausgewählt wird. Die Zahl wird nach jedem QSO um 1 erhöht und wird beim
Loggen im Feld Name gespeichert.
Bild 2 zeigt
das Fenster nach Eingabe von QSO-Daten.

Bild
2
Die grüne
Schrift im Feld "RDA" kennzeichnet diese RDA-Nr. als bereits
bestätigt. Wäre die Schrift rot, wäre es ein neuer RDA, und bei blauer Schrift
ist der RDA schon gearbeitet, aber noch nicht bestätigt.
Eine
Besonderheit sehen wir im unteren Teil des Fensters. Hier werden die bereits
getätigten QSOs mit der betreffenden Station angezeigt. Es handelt sich um das
ListIconGadget #LI_QSOB4.
Es wird
aktiviert, wenn festgestellt wird, dass im Log schon QSOs mit dem Rufzeichen
der Station gespeichert sind. Diese Anzeige kann aber auch zum Editieren der
QSO-Daten verwendet werden, z. B. um den Eingang der QSL-Karte zu vermerken.
Nun zurück
zum Programmablauf beim Starten.
Als
nächster Befehl wird die uns schon bekannte CallBack-Funktion festgelegt. Damit
kann man Ereignisse abfangen und auswerten, die von PureBasic nicht erfasst
werden. Wir haben eben schon davon Gebrauch gemacht. Als Parameter wird die
Adresse der CallBack-Funktion übergeben, @WindowCallBack().
Diese befindet sich in der Datei LogGenProc.pbi.
Nun wird
gleich die Tabelle Tab_Start geöffnet und ein
Wert für die QSL-Verwaltung abgefragt.
Der nächste
Befehl berechnet die Länge eines Punktes für die Morseausgabe.
Dann werden
die seriellen Schnittstellen für die Steuerung und die Tastung des Transceivers
geöffnet.
Danach wird
zur Prozedur SetWindow() gesprungen und dort die Grundeinstellung für das Betriebsfenster
vorgenommen. Als Erstes wird der Transceiver eingeschaltet. Dann wird der
Befehl "FA;" ausgegeben. Damit wird der Transceiver aufgefordert, die
Frequenz des VFO A zurückzugeben. Das macht er auch, und wir holen uns mit CommIn() diesen Wert. Daraus wird die Frequenz
extrahiert und mit Hilfe von Tab_Band in ein
Band umgesetzt und in einem Feld angezeigt.
Auf
dieselbe Art werden noch Betriebsart und Leistung abgefragt und angezeigt.
Jetzt wird WindowInit() ausgeführt.
Diese Funktion ist gedacht zum Einrichten des Fensters für Conteste, wurde
bisher aber kaum genutzt und wird bestimmt noch mal geändert. Das ist ein Indiz
dafür, dass eine selbst geschriebene Anwendung eine ewige Baustelle ist, weil
ständig neue Ideen einfließen können.
Als
nächstes werden noch abhängig von der Betriebsart einige Steuebefehle an den
Transceiver geschickt und die Variable sEigCall
mit dem eigenen Rufzeichen geladen. Ich bin nämlich noch Verantwortlicher für
DL0NRU, die Klubstation unseres OV H61. Bei der Ausgabe von Speichertexten wird
dann das jeweilige Rufzeichen eingesetzt.
Als letzte
Anweisung in SetWindow() wird das bisher noch unsichtbare Fenster sichtbar gemacht.
Die
Programmausführung springt jetzt zurück nach LogMain,
wo als zentraler Block des Programms auftretende Ereignisse in einer
Endlosschleife abgefragt werden.
Die Betriebsart PSK
Wenn wir im Menü Mode die Betriebsart PSK auswählen, wird die Prozedur
Set_PSK() in LogWndPSK.pbi
aufgerufen.
Als erstes wird das Fenster für die
Dauer der Umstrukturierung unsichtbar gemacht. Da wir jetzt mehr Platz
benötigen, wird das Fenster maximiert.
Dann werden die nicht benötigten
Gadgets versteckt und die weiterhin vorhandenen mit ResizeGadget()
an die richtige Stelle gerückt. Dann werden die Gadgets neu erstellt, die
bisher nicht vorhanden waren. Bild 3 zeigt das Fenster für PSK-Betrieb.
Bild 3
Der Block Log
ist identisch mit dem schon besprochenen. Rechts daneben gibt es auch einen
Block Speichertext, etwas umfangreicher als bei
CW.
Bei den Buttons Rig und Vorst. wird bei Linksklick der Text in
deutscher Sprache ausgegeben, bei Rechtsklick in englischer.
Ganz neu für uns sind auf dem
Fenster die beiden EditorGadgets, links für den Empfangstext und rechts für den
Sendetext.
#Edit_PSKRXTxt bietet
als Besonderheit die Möglichkeit, bestimmte Textpassagen mit Mausklick in die
dafür vorgesehenen Eingabefelder im Block Log zu übernehmen. Da für Editorgadgets keine
Mausereignisse vorgesehen sind, müssen wir wieder den Weg über WindowCallBack() wählen.
Dort wird aus der Mausposition das
nächste Zeichen errechnet und dann nach links und rechts von diesem Zeichen das
erste nicht alfanumerische Zeichen gesucht. Damit haben wir einen String
extrahiert. Aus der Anordnung von Ziffern und Buchstaben wird bestimmt, in
welches Eingabefeld der String gehört. Eine dreistellige Zahl wird in RST erh geschrieben, eine Kombination wie
"AA00AA" geht in das Feld Locator und eine Kombination von Buchstaben mit
mindestens einer Ziffer wird in das Feld Rufzeichen
geschrieben.
Reiner Alfatext kann Name oder QTH sein;
ein Linksklick wählt Name aus, ein Rechtsklick QTH.
Unkonventionelle Rufzeichen, die im
Aufbau wie ein Locator aussehen, werden auch dorthin geschrieben. Dann muss
eben Hand angelegt werden. So etwas kommt aber nur sehr selten vor.
Nun aber weiter beim Bild des
PSK-Fensters. Ein wesentliches Gadget für diese Betriebsart ist das ImageGadget
#Image_Wf. Hier wird der Wasserfall
dargestellt, der kennzeichnend für PSK ist.
Rechts daneben befinden sich die
Vektoranzeige sowie ein S-Meter.
Wie bekommen wir unseren Wasserfall?
Dazu müssen wir etwas weiter ausholen.
In LogConstGlob.pbi
haben wir unter PSK den Eintrag
"Global
lPSKCoreHandle.l = OpenLibrary(#PB_Any,
"PSKCore.DLL")"
"PSKCore.DLL" ist die
zentrale Datei, die alle Berechnungen ausführt sowie die Soundkarte verwaltet.
Sie wurde schon vor einigen Jahren von Moe Wheatley (AE4JY) zur Verfügung
gestellt. Die dazugehörige Dokumentation in Englisch kann nur als exzellent
bezeichnet werden.
Diese DLL stellt periodisch die für
die Wasserfalldarstellung notwendigen FFT-Daten zur Verfügung.
Unterhalb der eben genannten
Anweisung sehen wir Zeiger auf die von uns benutzten Funktionen der DLL. Diese
vorbereiteten Zeiger ermöglichen einen schnellen Dateizugriff.
Im unteren Drittel von Set_PSK() sehen wir, dass die Soundkarte mit einigen
dieser Funktionen auf den PSK-Betrieb vorbereitet wird. Auch wird ein Image
geschaffen, auf dem der Wasserfall dargestellt wird.
Als letzter Befehl steht dort
"OldWindowProc
= SetWindowLong_(hWnd, #GWL_WNDPROC, @NewWindowProc())."
Damit wird an Windows die Adresse
der Prozedur NewWindowProc() übergeben. In dieser
Prozedur werden die von der DLL übergebenen Daten bearbeitet.
NewWindowProc() steht oben in LogPSKProc.pbi. Wenn neue PSK-Daten zur Verfügung
stehen, wird #PSK_DataReady als Nachricht übergeben. Die FFT-Daten werden
aus dem Array lFFTData geholt und in Farbwerte
konvertiert, die dann in das Image geschrieben werden.
Die für die Pixelmanipulation
gedachten PureBasic-Befehle Plot() und Point() sind für unsere Zwecke zu langsam. Deshalb
wird wieder eine API-Funktion bemüht, um den Wasserfall zeitgerecht
darzustellen. Mit GetObject_() wird eine Bitmap
festgelegt, in die die Farbdaten geschrieben werden.
Das Image hat eine Breite von 640
und eine Höhe von 90 Pixeln sowie eine Farbtiefe von 32 Bit.
Der Vorgang beim Zeichnen des
Wasserfalls ist folgender:
Es werden die Pixel aus Zeile y
gelesen und an gleicher x-Position in Zeile y+1 geschrieben.
Die Farbwerte der neuesten FFT-Daten
werden immer in Zeile 0 geschrieben.
Die ältesten Daten befinden sich
also immer in Zeile 89 am unteren Rand des Wasserfalls.
Die direkte Verwendung der FFT-Daten
für die Farbwerte hat hier zu keinem befriedigenden Ergebnis geführt. Deshalb
ist jedem Wertebereich eine feste Farbe zugeordnet, wobei die höchsten Werte in
Rot , die mittleren in Gelb und die niedrigsten in Dunkelblau dargestellt
werden.
Bei einem Linksklick auf ein Signal
innerhalb des Wasserfalls wird dieses Signal dekodiert. Oberhalb des
Wasserfalls erscheint ein grüner Pfeil, der das Signal bzw. die Frequenz
markiert.
Wenn ein neues Zeichen bereitsteht,
wird #PSK_CharReady ausgegeben. Der ASCII-Code des Zeichens steht in wParam.
Ist wParam = 8, wird das letzte Zeichen
gelöscht. Ist wParam 10 oder 13, wird eine neue
Zeile begonnen, aber nur ein Mal. Damit werden mehrere Zeilenvorschübe
hintereinander verhindert.
Ein darstellbares Zeichen wird an
das RX-EditorGadget ausgegeben.
Sendetexte erscheinen auch im
RX-Fenster. Um sie als solche zu kennzeichnen, werden sie rot dargestellt. Die
Größe des im RX-EditorGadgets gespeicherten Textes wird auf maximal 5000
Zeichen festgelegt. Bei Überschreiten dieses Wertes wird der Text auf die
letzten 2500 Zeichen gekürzt.
Neben dem Wasserfall haben wir noch
zwei Pfeilbuttons, mit denen die Frequenz am Transceiver um 1 kHz erhöht oder
verringert kann. Das ist vorteilhaft, wenn z. B. bei einem PSK-Contest größere
Bandbereiche von Stationen belegt sind. Diese Aktion wird mit einem Linksklick
vorgenommen.
Es geschieht gelegentlich, dass
beide Stationen nicht ganz transceive sind. Dann muss man bei jedem Durchgang
des QSO-Partners die Empfangsfrequenz neu einstellen. Um das zu vermeiden, wird
mit einem Rechtsklick auf einen dieser Buttons die RIT eingeschaltet und die
Empfangsfrequenz um 10 Hz nach oben oder unten verschoben. Das hilft meistens
schon; wenn das nicht reicht, wird die Frequenz mit jedem weiteren Klick um
weitere 10 Hz verändert. Damit kann man das häufig ohne weitere
Abstimmmaßnahmen das QSO zu Ende führen.
Am Ende der Verbindung wird bei
Klick auf den Log-Button die RIT wieder abgeschaltet und die Ablage auf 0
gesetzt.
Unterhalb des Wasserfalls werden die
Frequenz des gewählten Signals sowie die IMD angezeigt. Die IMD-Anzeige
benötigt aber eine gewisse Zeit ohne Modulation, um zu einem Ergebnis zu
kommen.
Wir sind jetzt am Ende der
Beschreibung des Logprogramms angelangt. Wenn der Eine oder Andere durch diesen
Beitrag angeregt wird, es selbst einmal zu versuchen, dann ist sein Zweck
erfüllt. Für Fragen per EMail stehe ich
gerne zur Verfügung.