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%'

                        UNION

                        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.