Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51aa04bfb5 | ||
|
|
6b0d699a98 | ||
|
|
6b29ffe3ba | ||
|
|
08b109106e | ||
|
|
21b2d9970a | ||
|
|
94fc13e3c3 | ||
|
|
d475c6b2c4 | ||
|
|
211be6081d | ||
|
|
b550d79b4b | ||
|
|
352cdcceb2 | ||
|
|
905ce5766e | ||
|
|
2803f7d8e3 | ||
|
|
4a8e08a331 | ||
|
|
a24db90ad2 | ||
|
|
d91b119112 | ||
|
|
345c4adfc3 | ||
|
|
0e8cd06e43 | ||
|
|
7ff7248e7d | ||
|
|
3193e4ac73 | ||
|
|
3ec6cab46a | ||
|
|
b280f1b0db | ||
|
|
7fe33613e9 | ||
|
|
9378bf2afd | ||
|
|
047891e109 | ||
|
|
4f88101ab7 | ||
|
|
5955ba4ecf | ||
|
|
a9eba266ee | ||
|
|
4601199587 | ||
|
|
8c6ce6b402 | ||
|
|
75ab24da25 | ||
|
|
8aadbb9ee0
|
||
|
|
3b344482ba
|
||
|
|
86ad2f7a43
|
||
|
|
29ce6b0a4d
|
||
|
|
4d8b8aa12e
|
||
|
|
af30c17a0d | ||
|
|
399e5f34b7
|
||
|
|
2bc7554a52
|
||
|
|
77b7a4729f
|
||
|
|
56b2fae0a3
|
||
|
|
cc991418e8 | ||
|
|
399f7235ef | ||
|
|
720dd2560b | ||
|
|
8fa5360752 | ||
|
|
422e6cf4b7 | ||
|
|
f77e0bd8f9 | ||
|
|
641c9bdbc3 | ||
|
|
265115c9cc | ||
|
|
49c0c1eb37 | ||
|
|
88cb55520d | ||
|
|
f014b4697b
|
||
|
|
209034724f | ||
|
|
f8c04e72e1 | ||
|
|
a4475e6d12 | ||
|
|
e3b2ea725a | ||
|
|
6966edbfff | ||
|
|
42d4b72dd6 | ||
|
|
15f585c938 | ||
|
|
663f724c98 |
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: kst4contest-change
|
||||
description: Analyze or implement KST4Contest Java/JavaFX changes, bug fixes, refactorings, protocol handling, contest workflow behaviour, callsign/band logic, AirScout/logging/rotor/DXCluster integrations, threading and state management. Use current code as source of truth and follow the mandatory concept-and-question gate before edits.
|
||||
---
|
||||
|
||||
# KST4Contest change workflow
|
||||
|
||||
Read the relevant reference files before proposing a concept.
|
||||
|
||||
## Phase 1: read-only analysis
|
||||
|
||||
- Inspect the current code and tests.
|
||||
- Identify the current data flow and thread ownership.
|
||||
- Identify user-visible and protocol-visible behaviour.
|
||||
- Check relevant `docs/PROJECT_CONTEXT.md` sections when they exist.
|
||||
- Check whether the task overlaps a known invariant in the references.
|
||||
- Do not modify files.
|
||||
|
||||
## Phase 2: report understanding in German
|
||||
|
||||
Explain:
|
||||
|
||||
- what Marc wants changed;
|
||||
- what must remain unchanged;
|
||||
- which components appear affected;
|
||||
- what evidence in the current code supports that understanding;
|
||||
- any conflict between current code and historical project context.
|
||||
|
||||
Never resolve a conflict by guessing.
|
||||
|
||||
## Phase 3: questions and final concept in German
|
||||
|
||||
Before finalizing the concept:
|
||||
|
||||
- identify all material implementation choices;
|
||||
- ask Marc questions that are not already answered by code, project instructions or prior confirmed decisions;
|
||||
- wait for answers when needed.
|
||||
|
||||
Then present:
|
||||
|
||||
- intended data/control flow;
|
||||
- exact behavioural changes;
|
||||
- compatibility impact;
|
||||
- thread/UI impact;
|
||||
- persistence impact;
|
||||
- test strategy;
|
||||
- likely documentation impact;
|
||||
- likely durable project-context impact;
|
||||
- related-project impact when relevant.
|
||||
|
||||
Ask for explicit concept approval and wait before editing.
|
||||
|
||||
## Phase 4: implementation
|
||||
|
||||
After approval:
|
||||
|
||||
- implement the smallest coherent change;
|
||||
- preserve unrelated behaviour;
|
||||
- keep comments/Javadoc in English;
|
||||
- add/update focused tests;
|
||||
- keep external protocol parsing defensive;
|
||||
- avoid hidden defaulting for unknown values.
|
||||
|
||||
## Phase 5: verification and documentation impact
|
||||
|
||||
Run focused checks, then appropriate broader checks.
|
||||
|
||||
Because KST4Contest build configuration may ignore failures/findings, inspect summaries and reports rather than only command exit status.
|
||||
|
||||
Then use `$software-project-context`:
|
||||
|
||||
- do a low-cost documentation-impact classification;
|
||||
- inspect only likely affected manual/README/website sections;
|
||||
- update targeted documentation when clearly required by the approved implementation;
|
||||
- update `docs/PROJECT_CONTEXT.md` for significant durable technical decisions/state changes;
|
||||
- do not perform a full manual audit unless there is a specific trigger.
|
||||
|
||||
## Phase 6: report
|
||||
|
||||
Report in German:
|
||||
|
||||
- files changed;
|
||||
- implementation summary;
|
||||
- tests/checks;
|
||||
- warnings/findings;
|
||||
- documentation-impact result;
|
||||
- documentation/context updates or why none were required;
|
||||
- related-project impact when relevant;
|
||||
- unresolved issues;
|
||||
- no Git publication action unless explicitly requested.
|
||||
@@ -0,0 +1,7 @@
|
||||
interface:
|
||||
display_name: "KST4Contest Change"
|
||||
short_description: "Plan and implement KST4Contest changes safely"
|
||||
default_prompt: "Analyze the requested KST4Contest change, explain your understanding and concept in German, ask implementation questions, and wait for approval before editing."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,102 @@
|
||||
# Architecture context
|
||||
|
||||
## Current package shape
|
||||
|
||||
The repository currently contains major packages under `src/main/java/kst4contest/` including:
|
||||
|
||||
- `controller`
|
||||
- `locatorUtils`
|
||||
- `logic`
|
||||
- `model`
|
||||
- `service`
|
||||
- `test`
|
||||
- `utils`
|
||||
- `view`
|
||||
|
||||
Do not treat package names alone as proof of clean MVC boundaries. Inspect actual dependencies.
|
||||
|
||||
## Preferred message/member data flow
|
||||
|
||||
The established target architecture for active chat members is:
|
||||
|
||||
```text
|
||||
ON4KST / network
|
||||
|
|
||||
v
|
||||
MessageBusManagementThread
|
||||
|
|
||||
v
|
||||
ChatController
|
||||
|
|
||||
v
|
||||
thread-safe active-member domain state
|
||||
(ConcurrentMap; identity includes callsign + category)
|
||||
|
|
||||
v
|
||||
JavaFX ObservableList UI mirror
|
||||
|
|
||||
v
|
||||
FilteredList / SortedList / TableView / selection
|
||||
```
|
||||
|
||||
Key rule:
|
||||
|
||||
`ObservableList` is a JavaFX UI projection, not the canonical store for worker-thread logic.
|
||||
|
||||
`MessageBusManagementThread` must not directly read or modify the UI list.
|
||||
|
||||
## JavaFX boundary
|
||||
|
||||
UI-visible mutations belong on the JavaFX Application Thread.
|
||||
|
||||
Prefer controller-owned helpers such as an existing `runOnFxThread` abstraction when available; otherwise use `Platform.runLater` consistently.
|
||||
|
||||
Do not move business/data access into the FX thread merely to silence a threading problem.
|
||||
|
||||
## Parser/service separation
|
||||
|
||||
For protocol receivers, the preferred direction is:
|
||||
|
||||
```text
|
||||
Receiver (I/O only)
|
||||
-> Parser (wire data -> DTO)
|
||||
-> Service (domain/persistence logic)
|
||||
-> Controller (UI coordination)
|
||||
-> Observable UI model
|
||||
-> View
|
||||
```
|
||||
|
||||
A previous concrete example for UCXLog was:
|
||||
|
||||
```text
|
||||
UcxUdpReceiver
|
||||
-> UcxPacketParser
|
||||
-> DTO
|
||||
-> UcxLogService
|
||||
-> ChatController
|
||||
-> UI projection
|
||||
```
|
||||
|
||||
This is architectural guidance, not permission for a broad refactor. Apply only when it is in scope and approved.
|
||||
|
||||
## DTO preference
|
||||
|
||||
Prefer explicit DTO classes over records when introducing protocol/transport data structures in this project, unless the approved concept intentionally changes that convention.
|
||||
|
||||
## Null safety
|
||||
|
||||
Chat members can be incomplete, especially:
|
||||
|
||||
- fallback members;
|
||||
- historic message senders;
|
||||
- server-derived partial members.
|
||||
|
||||
Values such as QRB and QTF can be absent.
|
||||
|
||||
Rules:
|
||||
|
||||
- absence stays absence;
|
||||
- do not map `null` to numeric zero;
|
||||
- UI must render unavailable state safely;
|
||||
- sorting/filtering/calculation code must tolerate missing values;
|
||||
- unexpected missing values must not terminate worker or UI threads.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Automated messaging, beacons and skeds
|
||||
|
||||
## General
|
||||
|
||||
Automated replies/beacons must be conservative because they interact with the live ON4KST service.
|
||||
|
||||
## Established safety behaviour
|
||||
|
||||
Historical confirmed rules include:
|
||||
|
||||
- beacon/autoanswer scheduling shares controlled timing rather than spawning uncontrolled independent timers;
|
||||
- minimum interval has been tightened to avoid spam;
|
||||
- automated text length is bounded;
|
||||
- invalid or incomplete replies are rejected before transmission;
|
||||
- a cooldown is not consumed unless a complete valid reply enters the TX queue;
|
||||
- QRG/frequency requests take precedence where the current implementation defines that;
|
||||
- automated-message markers are ignored to prevent response loops.
|
||||
|
||||
Recent logic used a two-minute cooldown keyed by complete callsign plus chat category and ignored the project's own automated-message marker.
|
||||
|
||||
Treat exact marker strings and timing constants as current-code facts to verify, not values to recreate from memory.
|
||||
|
||||
## Monitoring
|
||||
|
||||
Station monitoring is intentionally base-call-wide:
|
||||
|
||||
Entering a variant such as:
|
||||
|
||||
```text
|
||||
DN9APW-2
|
||||
DN9APW-70
|
||||
```
|
||||
|
||||
monitors:
|
||||
|
||||
```text
|
||||
DN9APW
|
||||
```
|
||||
|
||||
This reduces manual configuration for sked monitoring.
|
||||
|
||||
Keep this separate from chat-member identity, which may require full suffix + category.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Domain, callsigns and bands
|
||||
|
||||
## Chat-member identity
|
||||
|
||||
Full callsign variants can be distinct chat identities.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
DN9APW
|
||||
DN9APW-2
|
||||
DN9APW-70
|
||||
```
|
||||
|
||||
Do not globally strip suffixes when identifying chat members.
|
||||
|
||||
Category is also part of identity. A practical key is conceptually equivalent to:
|
||||
|
||||
```text
|
||||
FULL_CALLSIGN|CATEGORY
|
||||
```
|
||||
|
||||
Do not allow messages from one category to attach to a same-looking member in another category.
|
||||
|
||||
## Base-call operations
|
||||
|
||||
Some features intentionally operate on the base callsign.
|
||||
|
||||
Confirmed examples:
|
||||
|
||||
### Worked state
|
||||
|
||||
Worked status is shared across suffix variants of the same base call.
|
||||
|
||||
If the base station has been worked on the relevant basis, variants such as `CALL-2`, `CALL-70`, `CALL-144`, `CALL-432` should not become independent worked identities merely because of the suffix.
|
||||
|
||||
### Monitoring
|
||||
|
||||
Monitoring a station entered as `DN9APW-2` or `DN9APW-70` should monitor the base call `DN9APW`.
|
||||
|
||||
This is intentional: a user monitoring another station's skeds should not have to create one monitor entry per SSID.
|
||||
|
||||
Do not extend base-call matching to unrelated features without approval.
|
||||
|
||||
## Suffix semantics
|
||||
|
||||
Do not assume a suffix always means a band or category.
|
||||
|
||||
Historical examples have shown the same base calls with different suffix conventions in different chat categories.
|
||||
|
||||
Therefore:
|
||||
|
||||
- preserve exact full-call identity where needed;
|
||||
- normalize only for explicitly approved base-call features;
|
||||
- never infer missing band/category semantics from the suffix alone.
|
||||
|
||||
## Categories
|
||||
|
||||
KST4Contest's central VHF/UHF usage focuses on ON4KST categories 2 and 3.
|
||||
|
||||
However, other categories can occur.
|
||||
|
||||
Rules:
|
||||
|
||||
- unsupported/uninteresting categories must be ignored or handled safely;
|
||||
- they must not produce index, switch or null errors;
|
||||
- do not let their values contaminate category-2/category-3 band logic.
|
||||
|
||||
## Band availability
|
||||
|
||||
Band activity can be derived from several signals including name/text parsing and explicit/manual information.
|
||||
|
||||
Confirmed invariant:
|
||||
|
||||
`NOT-QRV` overrides positive availability indications.
|
||||
|
||||
Known-active-band and `B+` handling should use one consistent interpretation across the program.
|
||||
|
||||
Do not implement separate slightly different parsers in multiple UI/features if a shared existing mechanism is available.
|
||||
|
||||
## Current QRG
|
||||
|
||||
Features that depend on propagation/band/frequency should use the current relevant QRG or the approved band calculation.
|
||||
|
||||
Never silently reintroduce a universal hardcoded 144 MHz fallback.
|
||||
|
||||
When a frequency is ambiguous and no approved fallback exists, ask rather than guessing.
|
||||
@@ -0,0 +1,115 @@
|
||||
# Known edge cases and regression patterns
|
||||
|
||||
This file is a regression-awareness list. Reproduce/inspect current code before deciding a historical bug still exists.
|
||||
|
||||
## Incomplete ChatMember
|
||||
|
||||
Known failure pattern:
|
||||
|
||||
```text
|
||||
Cannot invoke "java.lang.Double.intValue()" because
|
||||
ChatMember.getQrb() is null
|
||||
```
|
||||
|
||||
Lesson:
|
||||
|
||||
- QRB can be absent;
|
||||
- UI/calculation code must not blindly unbox/convert;
|
||||
- unavailable is not zero.
|
||||
|
||||
Apply the same reasoning to QTF and other server/fallback-derived fields.
|
||||
|
||||
## Callsign suffix collisions
|
||||
|
||||
A historical issue caused messages/identity problems between:
|
||||
|
||||
```text
|
||||
DN9APW
|
||||
DN9APW-2
|
||||
```
|
||||
|
||||
The corrective model is not "strip all suffixes".
|
||||
|
||||
Instead:
|
||||
|
||||
- keep full-call chat identities distinct;
|
||||
- include category;
|
||||
- use base call only for explicitly base-call-wide features such as worked state/monitoring.
|
||||
|
||||
## Unsupported chat categories
|
||||
|
||||
The ON4KST ecosystem can expose categories outside the two central ones.
|
||||
|
||||
A parser/switch/filter must not throw because the category is irrelevant to KST4Contest.
|
||||
|
||||
Safe ignore/fallback beats fake band assignment.
|
||||
|
||||
## AirScout higher-band queries
|
||||
|
||||
A historical observation showed aircraft visible in AirScout while API results for a 432 MHz case were empty.
|
||||
|
||||
Potential causes included frequency-string formatting.
|
||||
|
||||
Lesson:
|
||||
|
||||
- verify the exact upstream contract;
|
||||
- compare request produced by KST4Contest with a known-working request;
|
||||
- do not "fix" by guessing a string format or falling back to an unrelated band.
|
||||
|
||||
## Second airplane-scatter result / missing aircraft
|
||||
|
||||
A known UI failure involved missing/partial airplane-scatter data and a `TextInputControl` range error (`start must be <= end`).
|
||||
|
||||
Lesson:
|
||||
|
||||
- empty/partial AP results must be validated before text-range highlighting/selection;
|
||||
- second-result paths need the same null/range checks as primary results.
|
||||
|
||||
## Historic/unknown user message
|
||||
|
||||
A user/message record can refer to a callsign not present in the current member list.
|
||||
|
||||
Do not require current login membership to render or classify historic messages.
|
||||
|
||||
## UM3-style handling
|
||||
|
||||
Historical message handling included cases that should be ignored safely if the user is not in the chat/member state.
|
||||
|
||||
Lesson:
|
||||
|
||||
- external message types must tolerate missing member references.
|
||||
|
||||
## CR/LF and disconnect suspicion
|
||||
|
||||
Do not treat line endings as harmless text formatting in socket code.
|
||||
|
||||
When diagnosing a disconnect:
|
||||
|
||||
- inspect transmitted bytes;
|
||||
- inspect server response/EOF;
|
||||
- compare Windows versions only after proving the application sends different bytes;
|
||||
- avoid duplicated LF/CRLF terminators.
|
||||
|
||||
## Filter reset
|
||||
|
||||
A reset button that clears control values but leaves predicates active is not a valid reset.
|
||||
|
||||
Verify final predicate composition, not only UI state.
|
||||
|
||||
## Map render flicker
|
||||
|
||||
Leaflet/WebView render fragmentation under Java 21 was mitigated by:
|
||||
|
||||
```text
|
||||
window.L_DISABLE_3D = true
|
||||
```
|
||||
|
||||
before Leaflet load.
|
||||
|
||||
Do not remove as "obsolete CSS cleanup" without a visual regression check.
|
||||
|
||||
## Network start/reconnect loop
|
||||
|
||||
Initial connection failure must not spin indefinitely or block controlled recovery.
|
||||
|
||||
Connection state must be based on actual I/O lifecycle rather than only `Socket.isConnected()`-style historical state.
|
||||
@@ -0,0 +1,183 @@
|
||||
# Project behaviour catalog
|
||||
|
||||
This catalog summarizes behaviour established during prior KST4Contest work. It is context for analysis, not permission to overwrite newer code. Always inspect the current implementation before modifying a listed area.
|
||||
|
||||
## Core purpose
|
||||
|
||||
KST4Contest is an ON4KST-oriented desktop client optimized for VHF/UHF/microwave contest workflows.
|
||||
|
||||
Core areas developed over time include:
|
||||
|
||||
- simultaneous ON4KST chat handling;
|
||||
- priority candidates;
|
||||
- sked workflow and timeline;
|
||||
- worked-state synchronization;
|
||||
- logging integrations;
|
||||
- DXCluster;
|
||||
- AirScout / airplane-scatter assistance;
|
||||
- rotor/control integrations;
|
||||
- map/path visualization;
|
||||
- automated replies/beacons;
|
||||
- user filtering and reachability;
|
||||
- documentation and website/update-feed integration.
|
||||
|
||||
## Two chat categories
|
||||
|
||||
The application is designed around two simultaneous relevant chat categories in normal operation.
|
||||
|
||||
Important consequences:
|
||||
|
||||
- same-looking calls in different categories are not automatically the same chat identity;
|
||||
- category is part of message/member identity;
|
||||
- category-specific QRG/band settings must not leak into the other category;
|
||||
- unsupported categories must not crash shared logic.
|
||||
|
||||
## Priority candidates
|
||||
|
||||
Priority scoring has included factors such as:
|
||||
|
||||
- QTF match;
|
||||
- recent activity;
|
||||
- message count;
|
||||
- positive signal indications;
|
||||
- sked rate.
|
||||
|
||||
Do not change weighting/meaning as collateral work. Treat it as user-facing contest logic.
|
||||
|
||||
## Timeline / skeds
|
||||
|
||||
The sked timeline has used 30-minute lanes and visualized airplane-scatter probability windows.
|
||||
|
||||
Known historical AP strength levels:
|
||||
|
||||
- 100%;
|
||||
- 75%;
|
||||
- 50%.
|
||||
|
||||
Sked reminder presets have covered short contest-relevant lead times.
|
||||
|
||||
Do not hardcode historical display constants into new code without verifying the current view/model.
|
||||
|
||||
## Worked state
|
||||
|
||||
Worked state is loaded from persistence and updated live from supported logging inputs.
|
||||
|
||||
The simplified UI meaning has been "worked any" where the locator/worked indicator is concerned.
|
||||
|
||||
Worked state is base-call-wide across suffix variants where established.
|
||||
|
||||
When changing persistence or logging synchronization, verify:
|
||||
|
||||
- startup DB load;
|
||||
- live update;
|
||||
- suffix/base-call mapping;
|
||||
- band mapping;
|
||||
- 50/70 MHz support where applicable;
|
||||
- UI projection.
|
||||
|
||||
## Known active bands / B+
|
||||
|
||||
Known-active-band information is derived consistently across the application from available hints.
|
||||
|
||||
Historical work unified:
|
||||
|
||||
- band mentions in user names;
|
||||
- band mentions in text;
|
||||
- manual/global band information;
|
||||
- `B+`-style availability.
|
||||
|
||||
Explicit `NOT-QRV` overrides positive hints.
|
||||
|
||||
Avoid introducing a second parser with different semantics.
|
||||
|
||||
## Selection and send workflow
|
||||
|
||||
Established fast-workflow behaviour includes:
|
||||
|
||||
- selecting a new station prefills `/cq callsign`;
|
||||
- send text is geared toward minimal contest interaction;
|
||||
- if no target chat category is selected, Main is the established fallback.
|
||||
|
||||
These are intentional workflow decisions, not incidental UI details.
|
||||
|
||||
## DXCluster
|
||||
|
||||
DXCluster support has included:
|
||||
|
||||
- integrated display;
|
||||
- copyable lines;
|
||||
- `/cq` workflow support;
|
||||
- beacon monitoring;
|
||||
- QTF/bearing-related presentation.
|
||||
|
||||
Preserve locator semantics and avoid sender/receiver field confusion.
|
||||
|
||||
## Map / path view
|
||||
|
||||
Map work has included:
|
||||
|
||||
- Leaflet 1.9.4 in JavaFX WebView;
|
||||
- terrain/path information;
|
||||
- airplane-scatter integration;
|
||||
- target-station selection;
|
||||
- path-analysis visibility;
|
||||
- station-count/status information;
|
||||
- target reset that does not reset zoom.
|
||||
|
||||
A persistent right-side station-information panel has been reduced/removed in favour of more compact presentation in later UI work.
|
||||
|
||||
Before changing layout, inspect the current version because this area has been actively iterated.
|
||||
|
||||
## Reachability and filters
|
||||
|
||||
Filter work has separated reachability concerns from generic filters.
|
||||
|
||||
A Reset Filter control must reset the actual filter predicates, not just visual controls.
|
||||
|
||||
UI sorting/filtering must remain stable when backing data changes.
|
||||
|
||||
## Autoanswer and beacons
|
||||
|
||||
Automated messaging exists to reduce repetitive contest chat work without creating spam or feedback loops.
|
||||
|
||||
Important principles:
|
||||
|
||||
- conservative timing;
|
||||
- bounded text;
|
||||
- no loop on own automated markers;
|
||||
- only consume cooldown after a valid queued reply;
|
||||
- category/callsign-safe identity;
|
||||
- QRG requests handled with the intended precedence.
|
||||
|
||||
## Connection handling
|
||||
|
||||
Network reliability is contest-critical.
|
||||
|
||||
Work has explicitly targeted:
|
||||
|
||||
- accurate connected/disconnected state;
|
||||
- server disconnect detection;
|
||||
- reconnect behaviour on unstable links;
|
||||
- no infinite loop on initial connection failure;
|
||||
- visible connection-state indication in the UI.
|
||||
|
||||
Do not regress connection state into "socket object exists therefore connected".
|
||||
|
||||
## Historic messages
|
||||
|
||||
Historic/non-current chat senders may not have a complete live `ChatMember`.
|
||||
|
||||
Highlighting, display and parsing must tolerate users not currently logged in.
|
||||
|
||||
## Website and documentation
|
||||
|
||||
The application repository also contains:
|
||||
|
||||
- bilingual manual content;
|
||||
- documentation images;
|
||||
- automated documentation PDF build;
|
||||
- Eleventy website;
|
||||
- download/update metadata generation;
|
||||
- release-oriented website automation.
|
||||
|
||||
A user-visible feature change may therefore affect more than Java source.
|
||||
@@ -0,0 +1,101 @@
|
||||
# Protocols and external integrations
|
||||
|
||||
This file records stable rules plus historical context. For exact current wire formats, ports and frequency strings, inspect the current code and authoritative upstream documentation.
|
||||
|
||||
## ON4KST
|
||||
|
||||
KST4Contest depends on long-lived server communication where malformed commands or framing can lead to disconnects.
|
||||
|
||||
Rules:
|
||||
|
||||
- preserve exact protocol framing;
|
||||
- treat CR/LF changes as protocol changes, not formatting cleanup;
|
||||
- do not append extra line terminators without verification;
|
||||
- detect actual socket/server disconnects reliably;
|
||||
- initial connection failure must not create an uncontrolled infinite loop;
|
||||
- reconnect logic must tolerate unstable Internet access;
|
||||
- the UI should make disconnected state clearly visible where implemented.
|
||||
|
||||
If Windows-specific behaviour is suspected, do not assume Win10/Win11 line-ending semantics explain it without reproducing or tracing the bytes.
|
||||
|
||||
## UCXLog / DXLog UDP XML
|
||||
|
||||
`contactreplace` must be handled equivalently to `contactinfo` for whole-log broadcasts where applicable.
|
||||
|
||||
Historical raw-packet XML start detection included:
|
||||
|
||||
```text
|
||||
<?xml
|
||||
<contactinfo
|
||||
<contactreplace
|
||||
<RadioInfo
|
||||
```
|
||||
|
||||
DOM handling also included a fallback to `contactreplace`.
|
||||
|
||||
Before changing this path, inspect the current parser because the code may have been refactored since this behaviour was introduced.
|
||||
|
||||
Preferred layering:
|
||||
|
||||
```text
|
||||
UDP receiver -> parser -> DTO -> service/domain/DB -> controller -> UI
|
||||
```
|
||||
|
||||
## Win-Test
|
||||
|
||||
Historical integration uses UDP port 8721 for Win-Test information.
|
||||
|
||||
Do not hardcode this fact into unrelated logic. Verify current configuration before changing listener setup or band mapping.
|
||||
|
||||
Changes involving 50/70 MHz, worked state or frequency mapping must be consistent with other logging inputs.
|
||||
|
||||
## AirScout
|
||||
|
||||
KST4Contest integrates with AirScout path/airplane-scatter information.
|
||||
|
||||
Stable principles:
|
||||
|
||||
- propagation/path queries must reflect the current relevant frequency/band;
|
||||
- do not fall back to 144 MHz merely because older code did;
|
||||
- unsupported chat categories must fail safely;
|
||||
- frequency-string formatting is an external API contract and must be checked, not guessed.
|
||||
|
||||
Historical work included a temporary 430 MHz approximation for ambiguous higher-band handling. Treat that as historical context, not a permanent invariant. Inspect the current implementation before using or changing it.
|
||||
|
||||
## PSTRotator
|
||||
|
||||
The integration has evolved.
|
||||
|
||||
Historical project notes mention more than one control approach, and recent work included UDP control/feedback behaviour around a configurable control port and feedback on the next port, including SPID movement retry logic.
|
||||
|
||||
Therefore:
|
||||
|
||||
- inspect the current implementation before assuming TCP vs UDP;
|
||||
- inspect current settings/defaults;
|
||||
- do not copy an old port/transport assumption into new code;
|
||||
- preserve asynchronous JavaFX-safe handling;
|
||||
- preserve any verified retry sequence only if it still exists in current code/tests.
|
||||
|
||||
If current code and historical notes conflict, ask Marc after showing the conflict.
|
||||
|
||||
## DXCluster
|
||||
|
||||
DXCluster is integrated into the contest workflow.
|
||||
|
||||
Preserve:
|
||||
- copyable/usable cluster lines;
|
||||
- correct sender/receiver locator semantics;
|
||||
- safe handling of missing locator data.
|
||||
|
||||
A historical bug copied sender and receiver locators as equal; do not reintroduce that behaviour.
|
||||
|
||||
## Protocol-wide error handling
|
||||
|
||||
External data is not trusted to be complete.
|
||||
|
||||
Rules:
|
||||
|
||||
- validate before dereferencing;
|
||||
- unknown categories/bands/tags should degrade safely;
|
||||
- malformed packets must not kill long-running receiver/management threads;
|
||||
- logging should make the rejected input diagnosable without flooding normal operation.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Deferred and roadmap context
|
||||
|
||||
This file is background only. Do not implement these items merely because they are mentioned here.
|
||||
|
||||
## Propagation model
|
||||
|
||||
After the manual audit, Marc intends to revisit and improve KST4Contest propagation modelling.
|
||||
|
||||
Exploration areas include:
|
||||
|
||||
- higher-density Copernicus GLO-30 terrain sampling;
|
||||
- Fresnel-zone analysis;
|
||||
- diffraction modelling;
|
||||
- simplified ray tracing / multi-segment paths;
|
||||
- VHF/UHF/microwave contest applicability, including around 1296 MHz and above;
|
||||
- practical contest-oriented prediction rather than academic complexity for its own sake.
|
||||
|
||||
This requires a fresh concept before implementation.
|
||||
|
||||
## Rule
|
||||
|
||||
Roadmap context must never silently enlarge the scope of a current task.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Settings and data context
|
||||
|
||||
Inspect `Config`/settings classes and current UI before using these names; this list records important settings/concepts encountered during prior work.
|
||||
|
||||
## Band / station settings
|
||||
|
||||
Important concepts have included:
|
||||
|
||||
- `MYQRGFirstCat`;
|
||||
- `MYQRGSecondCat`;
|
||||
- manual station band information;
|
||||
- current/actual QTF;
|
||||
- known-active bands;
|
||||
- selected/current QRG.
|
||||
|
||||
Band-dependent features must use the correct category/station context.
|
||||
|
||||
## Antenna / path settings
|
||||
|
||||
Important concepts have included:
|
||||
|
||||
- `actualQTF`;
|
||||
- `antennaBeamWidthDeg`;
|
||||
- maximum QRB;
|
||||
- AirScout/path-analysis settings.
|
||||
|
||||
Missing QRB/QTF must remain unknown, not numeric zero.
|
||||
|
||||
## UI settings
|
||||
|
||||
Persisted UI behaviour has included:
|
||||
|
||||
- dark mode;
|
||||
- map/path-analysis visibility;
|
||||
- filters/reachability controls;
|
||||
- column visibility;
|
||||
- divider/layout state where implemented.
|
||||
|
||||
Do not reset persisted user choices as an incidental effect of a feature change.
|
||||
|
||||
## Logging / worked persistence
|
||||
|
||||
Worked information is persisted and updated through multiple input paths.
|
||||
|
||||
Before changing one path, compare semantics across:
|
||||
|
||||
- DB load on startup;
|
||||
- simple/manual log integration where present;
|
||||
- UCXLog/DXLog;
|
||||
- Win-Test;
|
||||
- other current logging inputs.
|
||||
|
||||
The goal is one worked-state interpretation regardless of source.
|
||||
|
||||
## Chat/message automation
|
||||
|
||||
Configuration has included:
|
||||
|
||||
- beacon/autoanswer enablement;
|
||||
- beacon defaults;
|
||||
- message limits/timers;
|
||||
- category-specific communication.
|
||||
|
||||
Do not duplicate timers or create per-feature scheduling that bypasses the shared safety model.
|
||||
|
||||
## Connection state
|
||||
|
||||
Connection-state UI must reflect actual ON4KST connection lifecycle.
|
||||
|
||||
Any new state enum/property should have a clear owner and thread boundary.
|
||||
|
||||
## Persistence rule
|
||||
|
||||
Do not change persisted keys/schema/semantics simply to make new code easier.
|
||||
|
||||
If a schema/key migration is required:
|
||||
|
||||
1. explain current and new format;
|
||||
2. describe backward compatibility;
|
||||
3. ask for approval before implementing.
|
||||
@@ -0,0 +1,100 @@
|
||||
# Build, tests, static analysis and release safety
|
||||
|
||||
## Maven
|
||||
|
||||
Use the repository Maven wrapper.
|
||||
|
||||
Windows:
|
||||
|
||||
```text
|
||||
.\mvnw.cmd test
|
||||
.\mvnw.cmd package
|
||||
```
|
||||
|
||||
Run narrower tests first when possible.
|
||||
|
||||
## Important Surefire behaviour
|
||||
|
||||
The project has used:
|
||||
|
||||
```xml
|
||||
<testFailureIgnore>true</testFailureIgnore>
|
||||
```
|
||||
|
||||
Therefore an exit code of zero is not sufficient evidence that all tests passed.
|
||||
|
||||
Always inspect:
|
||||
|
||||
- test counts;
|
||||
- failures;
|
||||
- errors;
|
||||
- skipped tests;
|
||||
- Surefire report output when necessary.
|
||||
|
||||
State exact results in the completion report.
|
||||
|
||||
## PMD and SpotBugs
|
||||
|
||||
PMD and SpotBugs are integrated, but their findings have historically not always failed the build.
|
||||
|
||||
Do not say "static analysis clean" unless the relevant reports/output were actually checked.
|
||||
|
||||
## Packaging
|
||||
|
||||
The build contains packaging/module-list consistency logic.
|
||||
|
||||
Changes involving modules, JavaFX modules, jpackage or `module-info.java` must check:
|
||||
|
||||
- `pom.xml`;
|
||||
- `packaging/` helpers;
|
||||
- module requirements;
|
||||
- packaging verification output.
|
||||
|
||||
Do not manually update only one copy of a generated/synchronized module list.
|
||||
|
||||
## Website
|
||||
|
||||
The website is Eleventy-based and has Node tests.
|
||||
|
||||
Inspect `website/package.json`, `website/test/` and current scripts before choosing exact commands.
|
||||
|
||||
Historical website validation included Node tests for generated version/update information.
|
||||
|
||||
## Documentation build
|
||||
|
||||
GitHub Actions generates documentation/PDF and site artefacts.
|
||||
|
||||
A local code build does not prove documentation/site CI will pass.
|
||||
|
||||
## Versioning
|
||||
|
||||
Do not change project version, semantic version, update feed, tag or release metadata unless explicitly requested.
|
||||
|
||||
## Git
|
||||
|
||||
Each of these needs separate authorization:
|
||||
|
||||
- stage;
|
||||
- commit;
|
||||
- push;
|
||||
- PR;
|
||||
- merge;
|
||||
- tag;
|
||||
- release.
|
||||
|
||||
When asked to commit, use a concise English commit message.
|
||||
|
||||
Do not stage unrelated files.
|
||||
|
||||
## Release communication
|
||||
|
||||
When a release is explicitly in scope, check:
|
||||
|
||||
- current changelog;
|
||||
- GitHub release/tag;
|
||||
- website download/update feed;
|
||||
- documentation;
|
||||
- HamRadioOnline download/manual destinations;
|
||||
- any SourceForge publication workflow currently used.
|
||||
|
||||
Do not assume an older deployment pipeline is still active.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Threading and state management
|
||||
|
||||
## Canonical state vs UI state
|
||||
|
||||
Use a thread-safe canonical state for data consumed by worker/network threads.
|
||||
|
||||
The active-member UI list is only a projection.
|
||||
|
||||
Preferred conceptual model:
|
||||
|
||||
```text
|
||||
ConcurrentMap<MemberKey, ChatMember> activeMembers
|
||||
|
|
||||
| FX-thread projection/update
|
||||
v
|
||||
ObservableList<ChatMember> activeMembersUi
|
||||
```
|
||||
|
||||
`MemberKey` semantics must preserve full callsign plus category unless the specific operation is intentionally base-call-wide.
|
||||
|
||||
## MessageBusManagementThread
|
||||
|
||||
Do not:
|
||||
|
||||
- iterate JavaFX `ObservableList` from the worker thread;
|
||||
- add/remove JavaFX-list entries directly from the worker thread;
|
||||
- use the FX thread as a substitute for proper domain state ownership.
|
||||
|
||||
Do:
|
||||
|
||||
- pass domain events/data to the controller/service boundary;
|
||||
- modify canonical thread-safe state outside UI code as appropriate;
|
||||
- project changes to JavaFX state on the FX thread.
|
||||
|
||||
## Controller boundary
|
||||
|
||||
`ChatController` is the preferred coordination boundary for UI-visible state.
|
||||
|
||||
Keep view-specific operations out of protocol receiver code.
|
||||
|
||||
## External receiver design
|
||||
|
||||
For receiver refactors, separate:
|
||||
|
||||
- socket/UDP/TCP I/O;
|
||||
- parsing;
|
||||
- DTO;
|
||||
- domain/persistence;
|
||||
- UI coordination.
|
||||
|
||||
## Error containment
|
||||
|
||||
Long-running threads must survive:
|
||||
|
||||
- malformed server records;
|
||||
- incomplete members;
|
||||
- unknown bands/categories;
|
||||
- missing locators;
|
||||
- null QRB/QTF;
|
||||
- temporary socket failure.
|
||||
|
||||
Catch errors at meaningful boundaries and include enough context in English diagnostic logs/comments to trace the input and stage of failure.
|
||||
|
||||
Do not swallow errors silently.
|
||||
|
||||
## JavaFX selection/sorting
|
||||
|
||||
When updating backing data:
|
||||
|
||||
- preserve current selection where the feature expects it;
|
||||
- avoid invalidating `FilteredList`/`SortedList` assumptions;
|
||||
- do not create recursive UI updates;
|
||||
- avoid accessing control state from worker threads.
|
||||
@@ -0,0 +1,65 @@
|
||||
# UI behaviour and workflow invariants
|
||||
|
||||
These are known user-experience decisions. Verify the current implementation before changing them.
|
||||
|
||||
## Selection and send text
|
||||
|
||||
A new station selection should prefill the established command form:
|
||||
|
||||
```text
|
||||
/cq callsign
|
||||
```
|
||||
|
||||
This behaviour is deliberate even when prior input text existed, according to the established workflow.
|
||||
|
||||
If no category is selected for sending, the established fallback is the Main category.
|
||||
|
||||
Do not change either behaviour as a side effect of unrelated refactoring.
|
||||
|
||||
## Map view
|
||||
|
||||
Known decisions:
|
||||
|
||||
- reset clears the target/station selection;
|
||||
- reset does not change the current zoom level;
|
||||
- selected-station information was moved toward the compact status line rather than requiring a persistent right-side detail panel;
|
||||
- path-analysis visibility is user-controllable and should not become undiscoverable;
|
||||
- map controls must remain usable in dark/light modes.
|
||||
|
||||
## Leaflet / JavaFX WebView
|
||||
|
||||
With Leaflet 1.9.4 under Java 21, fragmented rendering/flicker was fixed by disabling Leaflet CSS 3D transforms before Leaflet loads:
|
||||
|
||||
```text
|
||||
window.L_DISABLE_3D = true
|
||||
```
|
||||
|
||||
Do not remove/reorder this workaround without reproducing the rendering problem and proving the replacement.
|
||||
|
||||
## Filters
|
||||
|
||||
Known UI direction:
|
||||
|
||||
- Reset Filter must actually clear relevant filter predicates;
|
||||
- reachability controls are conceptually separate from generic filter controls;
|
||||
- reset control should remain visually discoverable;
|
||||
- truncated text should remain accessible through tooltips where implemented;
|
||||
- clickable links should remain functional in both themes.
|
||||
|
||||
## Priority / timeline
|
||||
|
||||
The contest workflow includes:
|
||||
- priority candidate presentation;
|
||||
- sked timeline;
|
||||
- activity/AP windows;
|
||||
- sked reminders.
|
||||
|
||||
Avoid UI changes that damage quick contest operation merely to make layout code simpler.
|
||||
|
||||
## Null display
|
||||
|
||||
Missing data is not `0`.
|
||||
|
||||
For QRB/QTF/locator/derived values, follow the current UI convention for unavailable/empty state.
|
||||
|
||||
Do not show a plausible-looking number when the model value is actually unknown.
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: kst4contest-documentation
|
||||
description: Perform targeted KST4Contest documentation-impact checks and update affected German/English manuals, README, website feature text, changelog, release notes and durable project context. Avoid full audits by default; keep documentation aligned with implemented behaviour and use the praktimarc-writing-style skill.
|
||||
---
|
||||
|
||||
# KST4Contest documentation workflow
|
||||
|
||||
Use `$software-project-context` and `$praktimarc-writing-style`.
|
||||
|
||||
## Default behaviour
|
||||
|
||||
Do not read the complete manual or website after every code change.
|
||||
|
||||
Start with a documentation-impact classification and search for the affected feature, setting, UI label, protocol/integration or operational concept.
|
||||
|
||||
Escalate to a broader audit only when:
|
||||
|
||||
- Marc explicitly requests it;
|
||||
- a major release is being prepared;
|
||||
- the change is broad across UI/workflows;
|
||||
- multiple targeted checks reveal wider drift.
|
||||
|
||||
## Before editing documentation
|
||||
|
||||
1. Inspect the actual implementation or approved specification.
|
||||
2. Explain in German what documentation is probably affected.
|
||||
3. Ask only unresolved behaviour/scope questions.
|
||||
4. If documentation updates are already part of an approved implementation concept, no second approval is required for obvious synchronisation.
|
||||
5. If documentation reveals a new material product decision, stop and ask Marc.
|
||||
|
||||
## Manuals
|
||||
|
||||
When user-facing behaviour is affected:
|
||||
|
||||
- search English and German manual content under `github_docs/` for the relevant feature/labels first;
|
||||
- inspect surrounding sections only;
|
||||
- keep both language versions semantically equivalent;
|
||||
- do not translate mechanically; English must be idiomatic;
|
||||
- preserve exact UI labels, values, callsigns, ports and protocol terminology;
|
||||
- document current behaviour only;
|
||||
- if code and manual disagree and it is unclear which behaviour is intended, report the conflict;
|
||||
- identify outdated/missing screenshots explicitly.
|
||||
|
||||
## README / website
|
||||
|
||||
Check only when the changed feature, installation, configuration, capability or compatibility is represented there or should reasonably be represented there.
|
||||
|
||||
- Keep feature descriptions concise.
|
||||
- Explain real contest/operating benefit, not marketing slogans.
|
||||
- Avoid duplicating large manual sections on the website.
|
||||
- Keep the main manual/download destinations consistent with the current site strategy.
|
||||
|
||||
## Durable project context
|
||||
|
||||
Update `docs/PROJECT_CONTEXT.md` for significant:
|
||||
|
||||
- architectural decisions;
|
||||
- threading/state ownership;
|
||||
- callsign/category semantics;
|
||||
- protocol/integration contracts;
|
||||
- persistence/configuration changes;
|
||||
- durable workarounds;
|
||||
- deployment/website relationships;
|
||||
- cross-project dependencies;
|
||||
- planned propagation/API architecture when it becomes concrete.
|
||||
|
||||
Keep current-state sections current rather than using the file as a raw changelog.
|
||||
|
||||
## Changelog / release notes
|
||||
|
||||
- Compact factual bullets.
|
||||
- Include user-visible changes and important reliability/compatibility fixes.
|
||||
- Do not invent version scope; derive it from actual commits/changelog/release context.
|
||||
- Keep release posts short and operationally relevant.
|
||||
@@ -0,0 +1,7 @@
|
||||
interface:
|
||||
display_name: "KST4Contest Documentation"
|
||||
short_description: "Keep KST4Contest manuals, website and release text aligned"
|
||||
default_prompt: "Review the implemented KST4Contest behaviour, explain the documentation impact in German, propose a concept, ask questions, and wait for approval before editing."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,26 @@
|
||||
# User-facing feature context
|
||||
|
||||
This is a documentation coverage reminder, not a canonical feature list. Verify each item in current code before documenting it.
|
||||
|
||||
Areas repeatedly documented or changed include:
|
||||
|
||||
- simultaneous ON4KST chat categories;
|
||||
- priority candidates;
|
||||
- sked timeline/reminders;
|
||||
- known active bands / B+ / NOT-QRV;
|
||||
- worked indicators;
|
||||
- DXCluster;
|
||||
- AirScout integration;
|
||||
- map/path analysis;
|
||||
- filtering and reachability;
|
||||
- PSTRotator/rotor integration;
|
||||
- UCXLog/DXLog and Win-Test log synchronization;
|
||||
- beacon/autoanswer behaviour;
|
||||
- connection status/reconnect behaviour;
|
||||
- dark/light UI behaviour;
|
||||
- QTF/bearing workflow;
|
||||
- download/update behaviour.
|
||||
|
||||
When one of these changes, search both language manuals and website copy for affected references.
|
||||
|
||||
Do not specialize documentation beyond actual behaviour. A useful example from prior work is callsign monitoring: entering an SSID-style variant can intentionally monitor the base call rather than requiring every suffix to be configured individually.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Manual and website context
|
||||
|
||||
## Manual location
|
||||
|
||||
KST4Contest documentation is maintained in `github_docs/` with English and German Markdown pages plus screenshots.
|
||||
|
||||
The documentation build is automated through repository workflows.
|
||||
|
||||
## Audit workflow established with Marc
|
||||
|
||||
The normal review method is:
|
||||
|
||||
1. compare documentation with actual code/behaviour;
|
||||
2. propose exact changes;
|
||||
3. note missing/outdated screenshots and their intended repo location;
|
||||
4. if code must change to match the manual, stop and confirm that code change first;
|
||||
5. keep German and English content aligned;
|
||||
6. prefer one thorough update over many cosmetic iterations.
|
||||
|
||||
With Codex editing locally, the old copy/paste insertion-guide step is replaced by direct edits, but the approval logic remains.
|
||||
|
||||
## Examples and easter eggs
|
||||
|
||||
Deliberate examples/test strings must not be "cleaned up" merely because they are informal.
|
||||
|
||||
A known example uses:
|
||||
|
||||
```text
|
||||
DO5AMF
|
||||
Testing DXC-Spot: Congrats, you donated $100!
|
||||
```
|
||||
|
||||
Preserve such deliberate easter eggs unless Marc explicitly asks to remove or replace them.
|
||||
|
||||
## Website
|
||||
|
||||
The website under `website/` uses Eleventy/Nunjucks.
|
||||
|
||||
Style direction:
|
||||
|
||||
- modern and concise;
|
||||
- technically focused;
|
||||
- no promotional tone;
|
||||
- English primary where appropriate;
|
||||
- documentation remains the detailed source; website text should not duplicate entire manual sections.
|
||||
|
||||
Current website architecture/scripts must be inspected before changes.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Release communications
|
||||
|
||||
## Changelog
|
||||
|
||||
Write concise English change descriptions.
|
||||
|
||||
Prioritize:
|
||||
|
||||
- behaviour users notice;
|
||||
- contest workflow impact;
|
||||
- protocol/integration compatibility;
|
||||
- bug/reliability fixes;
|
||||
- documentation improvements.
|
||||
|
||||
Avoid internal refactor trivia unless it materially changes reliability or maintainability relevant to the release.
|
||||
|
||||
## Social release post
|
||||
|
||||
Use `$praktimarc-writing-style`.
|
||||
|
||||
Typical structure:
|
||||
|
||||
- version;
|
||||
- short statement of what the release contains;
|
||||
- compact highlights;
|
||||
- operational context where relevant, e.g. preparation for a VUSHF contest or planned use at DM5M;
|
||||
- one clear download/manual destination.
|
||||
|
||||
Do not oversell.
|
||||
|
||||
## Download/manual direction
|
||||
|
||||
Marc has preferred routing users to the HamRadioOnline/KST4Contest download/manual pages rather than scattering multiple download links.
|
||||
|
||||
Before publishing new text, inspect the current website URLs and release setup instead of copying an old link.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: kst4contest-review
|
||||
description: Review current KST4Contest local changes or a proposed diff before commit. Check regressions, null safety, JavaFX threading, callsign/category semantics, band handling, protocol compatibility, tests, targeted documentation impact, durable project context and unintended scope. Report findings in German and do not modify files unless explicitly asked after the review.
|
||||
---
|
||||
|
||||
# KST4Contest review
|
||||
|
||||
Review first; do not edit during the review.
|
||||
|
||||
Read the relevant KST4Contest change references.
|
||||
|
||||
## Review priorities
|
||||
|
||||
1. Behaviour matches the approved concept.
|
||||
2. No unrelated changes.
|
||||
3. Full callsign/category identity remains correct.
|
||||
4. Base-call normalization is used only where intended.
|
||||
5. Null/unknown values are not converted to fake defaults.
|
||||
6. Worker threads do not manipulate JavaFX UI collections.
|
||||
7. FX-thread boundaries are correct.
|
||||
8. Protocol framing, CR/LF, XML and frequency formatting are unchanged unless explicitly intended.
|
||||
9. External malformed input cannot kill long-running threads.
|
||||
10. Tests cover the changed behaviour.
|
||||
11. Maven test output was interpreted correctly despite ignored-failure settings.
|
||||
12. A documentation-impact assessment was performed.
|
||||
13. Any likely affected manual/README/website sections match the implementation.
|
||||
14. `docs/PROJECT_CONTEXT.md` is updated when the change introduces a durable architectural/protocol/state/operational/integration decision.
|
||||
15. Comments/Javadoc are English.
|
||||
16. No unintended dependency/version/release changes.
|
||||
|
||||
Do not demand a full manual audit for an internal-only change when the impact assessment reasonably concludes there is no documentation effect.
|
||||
|
||||
## Report format
|
||||
|
||||
Report in German, ordered by severity.
|
||||
|
||||
For each finding include:
|
||||
|
||||
- affected file/location;
|
||||
- concrete problem;
|
||||
- consequence;
|
||||
- recommended correction.
|
||||
|
||||
Then include:
|
||||
|
||||
- verification gaps;
|
||||
- documentation-impact result;
|
||||
- durable-context gaps;
|
||||
- related-project gaps when relevant;
|
||||
- overall assessment.
|
||||
|
||||
Do not fix findings until Marc explicitly asks for implementation and the normal concept gate has been satisfied for the fixes.
|
||||
@@ -0,0 +1,7 @@
|
||||
interface:
|
||||
display_name: "KST4Contest Review"
|
||||
short_description: "Review KST4Contest diffs before commit"
|
||||
default_prompt: "Review the current KST4Contest changes only. Report findings in German and do not edit files."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,61 @@
|
||||
# Review checklist
|
||||
|
||||
## Scope
|
||||
|
||||
- Is every changed file necessary?
|
||||
- Did unrelated formatting or refactoring slip in?
|
||||
- Were user-authored local changes preserved?
|
||||
|
||||
## Architecture/threading
|
||||
|
||||
- Is canonical state owned outside JavaFX controls/lists?
|
||||
- Does worker code avoid `ObservableList` access?
|
||||
- Are UI mutations routed to the FX thread?
|
||||
- Are parser/I/O/domain/UI responsibilities clearer or at least not more coupled?
|
||||
|
||||
## Domain
|
||||
|
||||
- Full callsign + category identity preserved?
|
||||
- Base-call matching restricted to worked/monitoring or another explicitly approved feature?
|
||||
- Unknown category/band values safe?
|
||||
- NOT-QRV precedence preserved?
|
||||
- Missing QRB/QTF remains unavailable rather than zero?
|
||||
|
||||
## Protocols
|
||||
|
||||
- ON4KST framing unchanged unless approved?
|
||||
- CR/LF exact?
|
||||
- UCX `contactreplace` compatibility preserved?
|
||||
- Frequency strings verified rather than guessed?
|
||||
- PSTRotator/AirScout transport/API assumptions checked against current code?
|
||||
- Malformed input contained?
|
||||
|
||||
## UI
|
||||
|
||||
- Selection/focus/zoom/sorting preserved?
|
||||
- `/cq callsign` prefill behaviour preserved where relevant?
|
||||
- Main send-category fallback preserved where relevant?
|
||||
- map WebView workaround preserved?
|
||||
- null values displayed safely?
|
||||
|
||||
## Tests/build
|
||||
|
||||
- Focused regression test added or updated?
|
||||
- Test summary checked?
|
||||
- Ignored failures explicitly reported?
|
||||
- PMD/SpotBugs output considered?
|
||||
- packaging/module-list checks considered if modules changed?
|
||||
|
||||
## Documentation
|
||||
|
||||
- DE and EN manual both checked?
|
||||
- website/README/changelog checked if user-visible?
|
||||
- screenshot impact reported?
|
||||
- writing style applied?
|
||||
- no undocumented implementation or documented-but-unimplemented behaviour?
|
||||
|
||||
## Git/release
|
||||
|
||||
- No version bump unless requested?
|
||||
- No generated release/update-feed changes by accident?
|
||||
- No staging/commit/push without explicit authorization?
|
||||
@@ -0,0 +1,4 @@
|
||||
model_reasoning_effort = "high"
|
||||
approval_policy = "on-request"
|
||||
sandbox_mode = "workspace-write"
|
||||
web_search = "live"
|
||||
@@ -95,7 +95,8 @@ $endif$
|
||||
belowskip = 8pt,
|
||||
literate = {→}{{\ensuremath{\rightarrow}}}1
|
||||
{←}{{\ensuremath{\leftarrow}}}1
|
||||
{↔}{{\ensuremath{\leftrightarrow}}}1,
|
||||
{↔}{{\ensuremath{\leftrightarrow}}}1
|
||||
{…}{{\ldots}}1,
|
||||
}
|
||||
|
||||
%% ─── Blockquotes ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -16,8 +16,12 @@ on:
|
||||
options: ["false", "true"]
|
||||
default: "false"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
AUR_SSH_DIR: /tmp/aur-ssh
|
||||
|
||||
jobs:
|
||||
publish-aur:
|
||||
@@ -38,6 +42,11 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ssh-key: ${{ secrets.WEBSITE_DEPLOY_KEY }}
|
||||
|
||||
- name: Mark workspace as safe Git directory
|
||||
run: |
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Resolve release version
|
||||
id: ver
|
||||
@@ -94,6 +103,10 @@ jobs:
|
||||
sed -i "s/^pkgver=.*/pkgver=${PKGVER}/" packaging/aur/kst4contest/PKGBUILD
|
||||
sed -i "s/^sha256sums=.*/sha256sums=('${SHA_SRC}')/" packaging/aur/kst4contest/PKGBUILD
|
||||
|
||||
# A new pkgver supersedes any rebuild-only pkgrel bumps made in between.
|
||||
sed -i "s/^pkgrel=.*/pkgrel=1/" packaging/aur/kst4contest-bin/PKGBUILD
|
||||
sed -i "s/^pkgrel=.*/pkgrel=1/" packaging/aur/kst4contest/PKGBUILD
|
||||
|
||||
# kst4contest-git: Basis aus pom.xml, Suffix aus git
|
||||
BASE_VER=$(grep -m1 '<version>' pom.xml \
|
||||
| sed 's/.*<version>\(.*\)<\/version>.*/\1/' | sed 's/[-.]nightly//')
|
||||
@@ -114,52 +127,74 @@ jobs:
|
||||
cat "packaging/aur/${pkg}/.SRCINFO"
|
||||
done
|
||||
|
||||
- name: Commit updated PKGBUILDs to repo
|
||||
if: inputs.dry_run != 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "github-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git"
|
||||
git add packaging/aur/
|
||||
git diff --cached --quiet && echo "No PKGBUILD changes to commit." && exit 0
|
||||
git commit -m "chore: update AUR packages to ${{ steps.ver.outputs.tag }} [skip ci]"
|
||||
git push
|
||||
|
||||
- name: Set up AUR SSH
|
||||
if: inputs.dry_run != 'true'
|
||||
env:
|
||||
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur_ed25519
|
||||
chmod 600 ~/.ssh/aur_ed25519
|
||||
ssh-keyscan -t ed25519 aur.archlinux.org >> ~/.ssh/known_hosts
|
||||
cat >> ~/.ssh/config << 'EOF'
|
||||
Host aur.archlinux.org
|
||||
IdentityFile ~/.ssh/aur_ed25519
|
||||
User aur
|
||||
EOF
|
||||
mkdir -p "${AUR_SSH_DIR}"
|
||||
|
||||
printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" \
|
||||
> "${AUR_SSH_DIR}/aur_ed25519"
|
||||
|
||||
sed -i 's/\r$//' "${AUR_SSH_DIR}/aur_ed25519"
|
||||
chmod 600 "${AUR_SSH_DIR}/aur_ed25519"
|
||||
|
||||
ssh-keygen -y \
|
||||
-f "${AUR_SSH_DIR}/aur_ed25519" \
|
||||
> /dev/null
|
||||
|
||||
ssh-keyscan \
|
||||
-T 10 \
|
||||
-t ed25519 \
|
||||
aur.archlinux.org \
|
||||
> "${AUR_SSH_DIR}/known_hosts"
|
||||
|
||||
if [ ! -s "${AUR_SSH_DIR}/known_hosts" ]; then
|
||||
echo "::error::No SSH host key was received from aur.archlinux.org."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Received AUR host-key fingerprint:"
|
||||
ssh-keygen -lf "${AUR_SSH_DIR}/known_hosts"
|
||||
|
||||
if ! ssh-keygen -lf "${AUR_SSH_DIR}/known_hosts" \
|
||||
| grep -Fq "SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4"; then
|
||||
echo "::error::The AUR SSH host-key fingerprint does not match the official fingerprint."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' \
|
||||
"Host aur.archlinux.org" \
|
||||
" HostName aur.archlinux.org" \
|
||||
" User aur" \
|
||||
" IdentityFile ${AUR_SSH_DIR}/aur_ed25519" \
|
||||
" IdentitiesOnly yes" \
|
||||
" StrictHostKeyChecking yes" \
|
||||
" UserKnownHostsFile ${AUR_SSH_DIR}/known_hosts" \
|
||||
> "${AUR_SSH_DIR}/config"
|
||||
|
||||
chmod 600 "${AUR_SSH_DIR}/config"
|
||||
chmod 600 "${AUR_SSH_DIR}/known_hosts"
|
||||
|
||||
- name: Push to AUR
|
||||
if: inputs.dry_run != 'true'
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
GIT_SSH_COMMAND: ssh -F /tmp/aur-ssh/config
|
||||
run: |
|
||||
git config --global user.email "philipp@wagnersnetz.de"
|
||||
git config --global user.name "Philipp Wagner"
|
||||
|
||||
mkdir -p /tmp/aur
|
||||
|
||||
push_to_aur() {
|
||||
local pkg="$1"
|
||||
local msg="$2"
|
||||
local aur_dir="/tmp/aur/${pkg}"
|
||||
|
||||
git clone "ssh://aur@aur.archlinux.org/${pkg}.git" "${aur_dir}" 2>/dev/null || {
|
||||
mkdir -p "${aur_dir}"
|
||||
git -C "${aur_dir}" init
|
||||
git -C "${aur_dir}" remote add origin "ssh://aur@aur.archlinux.org/${pkg}.git"
|
||||
}
|
||||
git -c init.defaultBranch=master clone \
|
||||
"ssh://aur@aur.archlinux.org/${pkg}.git" "${aur_dir}"
|
||||
|
||||
cp "packaging/aur/${pkg}/PKGBUILD" "${aur_dir}/"
|
||||
cp "packaging/aur/${pkg}/.SRCINFO" "${aur_dir}/"
|
||||
@@ -175,3 +210,19 @@ jobs:
|
||||
push_to_aur kst4contest "Update to ${TAG}"
|
||||
push_to_aur kst4contest-git \
|
||||
"Update pkgver to $(grep '^pkgver=' packaging/aur/kst4contest-git/PKGBUILD | cut -d= -f2)"
|
||||
|
||||
- name: Commit updated PKGBUILDs to repo
|
||||
if: inputs.dry_run != 'true'
|
||||
run: |
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "github-actions[bot]"
|
||||
git add packaging/aur/
|
||||
git diff --cached --quiet && echo "No PKGBUILD changes to commit." && exit 0
|
||||
git commit -m "chore: update AUR packages to ${{ steps.ver.outputs.tag }} [skip ci]"
|
||||
|
||||
if ! git push; then
|
||||
echo "::warning::PKGBUILD bookkeeping commit could not be pushed to main."
|
||||
echo "The AUR packages were published; only the in-repo copy stays behind."
|
||||
echo "Check that WEBSITE_DEPLOY_KEY still has write access and may bypass"
|
||||
echo "the branch ruleset, or commit packaging/aur/ by hand."
|
||||
fi
|
||||
|
||||
@@ -65,6 +65,7 @@ jobs:
|
||||
github_docs/en-Home.md \
|
||||
github_docs/en-Installation.md \
|
||||
github_docs/en-Configuration.md \
|
||||
github_docs/en-Contest-Workflow.md \
|
||||
github_docs/en-Features.md \
|
||||
github_docs/en-User-Interface.md \
|
||||
github_docs/en-Macros-and-Variables.md \
|
||||
@@ -91,6 +92,7 @@ jobs:
|
||||
github_docs/de-Home.md \
|
||||
github_docs/de-Installation.md \
|
||||
github_docs/de-Konfiguration.md \
|
||||
github_docs/de-Contest-Workflow.md \
|
||||
github_docs/de-Funktionen.md \
|
||||
github_docs/de-Benutzeroberflaeche.md \
|
||||
github_docs/de-Makros-und-Variablen.md \
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
paths:
|
||||
- "src/**"
|
||||
- "packaging/icons/**"
|
||||
- "packaging/macos/**"
|
||||
- "pom.xml"
|
||||
- "mvnw"
|
||||
- "mvnw.cmd"
|
||||
@@ -63,6 +64,8 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
||||
$addModules = & java packaging/AddModules.java
|
||||
if ($LASTEXITCODE -ne 0) { throw "Failed to resolve --add-modules from module-info.java" }
|
||||
jpackage `
|
||||
--type app-image `
|
||||
--name praktiKST `
|
||||
@@ -71,7 +74,7 @@ jobs:
|
||||
--main-jar app.jar `
|
||||
--main-class kst4contest.view.Kst4ContestApplication `
|
||||
--module-path target/dist-libs `
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec `
|
||||
--add-modules $addModules `
|
||||
--dest dist
|
||||
|
||||
- name: Create Windows ZIP
|
||||
@@ -120,15 +123,16 @@ jobs:
|
||||
- name: Build app-image with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
|
||||
- name: Create AppDir metadata
|
||||
@@ -206,15 +210,16 @@ jobs:
|
||||
- name: Build Debian package
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type deb \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--linux-package-deps "libgstreamer1.0-0,libgstreamer-plugins-base1.0-0,gstreamer1.0-plugins-good" \
|
||||
--dest dist
|
||||
DEB="$(ls dist/*.deb | head -n 1)"
|
||||
@@ -266,15 +271,16 @@ jobs:
|
||||
- name: Build Fedora package
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type rpm \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--linux-package-deps "gstreamer1,gstreamer1-plugins-base,gstreamer1-plugins-good" \
|
||||
--dest dist
|
||||
RPM="$(ls dist/*.rpm | head -n 1)"
|
||||
@@ -328,15 +334,16 @@ jobs:
|
||||
- name: Build app-image with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
|
||||
- name: Build Arch Linux package artifact
|
||||
@@ -447,15 +454,16 @@ jobs:
|
||||
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
mkdir -p target/flatpak-src
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest target/flatpak-src
|
||||
|
||||
- name: Create Flatpak manifest
|
||||
@@ -601,6 +609,7 @@ jobs:
|
||||
name: Build macOS DMG (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, macos-15-intel]
|
||||
|
||||
@@ -627,33 +636,38 @@ jobs:
|
||||
- name: Ensure mvnw is executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Build JAR and copy runtime dependencies
|
||||
run: |
|
||||
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
|
||||
- name: Build macOS DMG with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
jpackage \
|
||||
--type dmg \
|
||||
--name KST4Contest \
|
||||
--icon packaging/icons/kst4contest.icns \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--dest dist
|
||||
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "13.0"
|
||||
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
|
||||
run: ./packaging/macos/ci-import-cert.sh
|
||||
|
||||
# Builds the jar, signs the app bundle and every native library inside it,
|
||||
# wraps it into a DMG and has Apple notarize the result. Same script the
|
||||
# local Mac uses, so the two cannot drift apart.
|
||||
- name: Build signed and notarized DMG
|
||||
env:
|
||||
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||
NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
NOTARY_ISSUER: ${{ secrets.MACOS_NOTARY_ISSUER }}
|
||||
run: |
|
||||
printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8"
|
||||
export NOTARY_KEY="$RUNNER_TEMP/notary.p8"
|
||||
./packaging/macos/build-signed-dmg.sh
|
||||
|
||||
- name: Remove signing credentials
|
||||
if: always()
|
||||
run: |
|
||||
rm -f "$RUNNER_TEMP/notary.p8"
|
||||
if [ -n "${SIGNING_KEYCHAIN:-}" ]; then
|
||||
security delete-keychain "$SIGNING_KEYCHAIN" || true
|
||||
fi
|
||||
|
||||
- name: Rename DMG artifact
|
||||
run: |
|
||||
DMG=$(ls dist/*.dmg | head -n 1)
|
||||
if [ -z "$DMG" ]; then
|
||||
echo "No DMG produced by jpackage" && exit 1
|
||||
echo "No DMG produced by the build" && exit 1
|
||||
fi
|
||||
mv "$DMG" "dist/${ASSET_BASENAME}-macos-${ARCH}.dmg"
|
||||
|
||||
|
||||
@@ -27,5 +27,8 @@ jobs:
|
||||
- name: Ensure mvnw is executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Verify packaging module list matches module-info.java
|
||||
run: java packaging/AddModules.java --verify-pom
|
||||
|
||||
- name: Compile
|
||||
run: ./mvnw -B -DskipTests compile
|
||||
|
||||
@@ -7,7 +7,9 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
issues: read
|
||||
packages: write
|
||||
|
||||
env:
|
||||
@@ -46,6 +48,8 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
||||
$addModules = & java packaging/AddModules.java
|
||||
if ($LASTEXITCODE -ne 0) { throw "Failed to resolve --add-modules from module-info.java" }
|
||||
jpackage `
|
||||
--type app-image `
|
||||
--name praktiKST `
|
||||
@@ -54,7 +58,7 @@ jobs:
|
||||
--main-jar app.jar `
|
||||
--main-class kst4contest.view.Kst4ContestApplication `
|
||||
--module-path target/dist-libs `
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec `
|
||||
--add-modules $addModules `
|
||||
--dest dist
|
||||
|
||||
- name: Create Windows ZIP
|
||||
@@ -96,15 +100,16 @@ jobs:
|
||||
- name: Build app-image with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
|
||||
- name: Create AppDir metadata
|
||||
@@ -175,15 +180,16 @@ jobs:
|
||||
- name: Build Debian package
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type deb \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--linux-package-deps "libgstreamer1.0-0,libgstreamer-plugins-base1.0-0,gstreamer1.0-plugins-good" \
|
||||
--dest dist
|
||||
DEB="$(ls dist/*.deb | head -n 1)"
|
||||
@@ -228,15 +234,16 @@ jobs:
|
||||
- name: Build Fedora package
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type rpm \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--linux-package-deps "gstreamer1,gstreamer1-plugins-base,gstreamer1-plugins-good" \
|
||||
--dest dist
|
||||
RPM="$(ls dist/*.rpm | head -n 1)"
|
||||
@@ -281,15 +288,16 @@ jobs:
|
||||
- name: Build app-image with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
|
||||
- name: Build Arch Linux package artifact
|
||||
@@ -391,15 +399,16 @@ jobs:
|
||||
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
mkdir -p target/flatpak-src
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--icon src/main/resources/icons/kst4contest.png \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest target/flatpak-src
|
||||
|
||||
- name: Create Flatpak manifest
|
||||
@@ -497,6 +506,7 @@ jobs:
|
||||
name: Build macOS DMG (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, macos-15-intel]
|
||||
|
||||
@@ -513,34 +523,39 @@ jobs:
|
||||
- name: Ensure mvnw is executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Build JAR and copy runtime dependencies
|
||||
run: |
|
||||
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
|
||||
- name: Build macOS DMG with jpackage
|
||||
run: |
|
||||
mkdir -p dist
|
||||
jpackage \
|
||||
--type dmg \
|
||||
--name KST4Contest \
|
||||
--icon packaging/icons/kst4contest.icns \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--dest dist
|
||||
|
||||
- name: Import signing certificate
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "13.0"
|
||||
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
|
||||
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
|
||||
run: ./packaging/macos/ci-import-cert.sh
|
||||
|
||||
# Builds the jar, signs the app bundle and every native library inside it,
|
||||
# wraps it into a DMG and has Apple notarize the result. Same script the
|
||||
# local Mac uses, so the two cannot drift apart.
|
||||
- name: Build signed and notarized DMG
|
||||
env:
|
||||
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||
NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||
NOTARY_ISSUER: ${{ secrets.MACOS_NOTARY_ISSUER }}
|
||||
run: |
|
||||
printf '%s' "$MACOS_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8"
|
||||
export NOTARY_KEY="$RUNNER_TEMP/notary.p8"
|
||||
./packaging/macos/build-signed-dmg.sh
|
||||
|
||||
- name: Remove signing credentials
|
||||
if: always()
|
||||
run: |
|
||||
rm -f "$RUNNER_TEMP/notary.p8"
|
||||
if [ -n "${SIGNING_KEYCHAIN:-}" ]; then
|
||||
security delete-keychain "$SIGNING_KEYCHAIN" || true
|
||||
fi
|
||||
|
||||
- name: Rename DMG artifact
|
||||
run: |
|
||||
ARCH=$(uname -m)
|
||||
DMG=$(ls dist/*.dmg | head -n 1)
|
||||
if [ -z "$DMG" ]; then
|
||||
echo "No DMG produced by jpackage" && exit 1
|
||||
echo "No DMG produced by the build" && exit 1
|
||||
fi
|
||||
mv "$DMG" "dist/KST4Contest-${{ github.ref_name }}-macos-${ARCH}.dmg"
|
||||
|
||||
@@ -698,6 +713,9 @@ jobs:
|
||||
- publish-flatpak-repo
|
||||
|
||||
steps:
|
||||
- name: Checkout release source
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download Windows artifact
|
||||
uses: actions/download-artifact@v4.1.3
|
||||
with:
|
||||
@@ -767,4 +785,46 @@ jobs:
|
||||
release-assets/flatpakref/*.flatpakref,
|
||||
release-assets/macos/KST4Contest-${{ github.ref_name }}-macos-*.dmg,
|
||||
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-en.pdf,
|
||||
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-de.pdf
|
||||
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-de.pdf
|
||||
|
||||
# The update feed is generated only after GitHub has published the
|
||||
# release. Otherwise the Releases API cannot return the release notes
|
||||
# belonging to the tag which triggered this workflow.
|
||||
- name: Set up Node.js for website build
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
cache-dependency-path: website/package-lock.json
|
||||
|
||||
- name: Build and validate website after release publication
|
||||
working-directory: website
|
||||
run: |
|
||||
npm ci
|
||||
npm test
|
||||
npm run build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Verify Stable release in update feed
|
||||
if: ${{ !startsWith(github.ref_name, 'beta-') }}
|
||||
working-directory: website
|
||||
run: npm run validate:version-info
|
||||
env:
|
||||
EXPECTED_STABLE_VERSION: ${{ github.ref_name }}
|
||||
|
||||
- name: Attach verified version info to tagged release
|
||||
run: >-
|
||||
gh release upload "${GITHUB_REF_NAME}"
|
||||
website/_site/kst4ContestVersionInfo.xml
|
||||
--clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload verified website artifact
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
with:
|
||||
name: kst4contest-website-${{ github.ref_name }}
|
||||
path: website/_site/
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
@@ -44,3 +44,6 @@ website/_site/
|
||||
|
||||
# Local secrets for act testing
|
||||
.secrets
|
||||
|
||||
# Apple notarization private keys - never commit these
|
||||
*.p8
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
# KST4Contest agent instructions
|
||||
|
||||
These project instructions extend Marc's global Codex working agreements.
|
||||
|
||||
## Project identity
|
||||
|
||||
KST4Contest is a Java/JavaFX desktop client for ON4KST chat with contest-oriented workflows and integrations including logging software, AirScout, rotor control, DXCluster and local persistence.
|
||||
|
||||
Primary repository areas:
|
||||
|
||||
- `src/main/java/kst4contest/`
|
||||
- `src/test/` where present;
|
||||
- `github_docs/`
|
||||
- `website/`
|
||||
- `docs/`
|
||||
- `packaging/`
|
||||
- `.github/`
|
||||
- `pom.xml`
|
||||
|
||||
Inspect the current tree before assuming an exact class/path still exists.
|
||||
|
||||
## Mandatory interaction rule
|
||||
|
||||
For every planned code or documentation implementation:
|
||||
|
||||
1. inspect first;
|
||||
2. explain the task understanding in German;
|
||||
3. identify and ask all relevant implementation questions;
|
||||
4. wait for answers when needed;
|
||||
5. present the final concept in German;
|
||||
6. state what must remain unchanged;
|
||||
7. request explicit concept approval;
|
||||
8. wait;
|
||||
9. implement only after approval.
|
||||
|
||||
If an answer is uncertain, do not interpolate it. Check current code/tests/docs/project context first and ask Marc when the uncertainty can affect behaviour.
|
||||
|
||||
## Language
|
||||
|
||||
- Communicate with Marc in German.
|
||||
- Write source-code comments and Javadoc exclusively in English.
|
||||
- Keep log/protocol/API literals in their canonical form.
|
||||
- Commit messages are concise English when a commit is explicitly requested.
|
||||
- User-facing DE/EN documentation follows `$praktimarc-writing-style`.
|
||||
|
||||
## Java and JavaFX architecture
|
||||
|
||||
- Preserve or improve separation between network/parsing/service/controller/UI responsibilities.
|
||||
- Do not solve architecture problems by letting worker/model code directly manipulate JavaFX UI collections.
|
||||
- Active chat-member domain state is conceptually thread-safe state; JavaFX `ObservableList` data is a UI projection, not the canonical worker-thread store.
|
||||
- `MessageBusManagementThread` must not directly read or mutate the JavaFX `ObservableList` used by the UI.
|
||||
- Route UI-visible mutations through the controller and the JavaFX Application Thread (`Platform.runLater` or the project's equivalent helper).
|
||||
- Prefer explicit DTOs over records when introducing transport/parser DTOs in this codebase unless the approved concept says otherwise.
|
||||
- Handle incomplete external/historical data defensively.
|
||||
- `qrb`, QTF and related external values can be absent. `null` means unavailable, not zero.
|
||||
- Unexpected input must not terminate message-processing or UI threads.
|
||||
|
||||
## Callsign and category identity
|
||||
|
||||
- Preserve full callsign variants as distinct chat-member identities where the server exposes them separately.
|
||||
- Category is part of chat identity. Do not merge messages across categories.
|
||||
- Base-call normalization may be used only for explicitly base-call-wide features such as worked status or monitoring rules.
|
||||
- Worked status is shared across suffix variants of the same base call.
|
||||
- Monitoring a callsign variant such as `DN9APW-2` or `DN9APW-70` is intended to monitor the base call `DN9APW`, so users do not need to enter every SSID.
|
||||
- Do not generalize suffix semantics beyond behaviour explicitly established by the current code/specification.
|
||||
|
||||
## Bands and availability
|
||||
|
||||
- ON4KST categories 2 and 3 are central to the normal VHF/UHF workflow, but other category values can occur and must fail safely.
|
||||
- Do not let unsupported categories produce exceptions.
|
||||
- Known-active-band logic and `B+` interpretation must remain consistent across the application.
|
||||
- Band information parsed from names/text must respect explicit `NOT-QRV` information; NOT-QRV overrides positive availability hints.
|
||||
- Do not silently fall back to a fixed band/frequency when a required decision is ambiguous unless an approved fallback exists.
|
||||
- Manual band settings and actual current QRG must remain consistent with features that depend on frequency.
|
||||
|
||||
## External protocols and integrations
|
||||
|
||||
Before changing ON4KST, AirScout, UCXLog/DXLog, Win-Test, PSTRotator or DXCluster handling:
|
||||
|
||||
- inspect the current implementation;
|
||||
- preserve exact framing and compatibility;
|
||||
- inspect current tests;
|
||||
- check authoritative upstream documentation when the protocol detail is uncertain;
|
||||
- ask Marc if more than one behaviour is plausible.
|
||||
|
||||
Specific invariants and historical context are in `$kst4contest-change` references.
|
||||
|
||||
Never change CR/LF, XML framing, callsign normalization, frequency formatting or port/transport assumptions casually.
|
||||
|
||||
## UI behaviour
|
||||
|
||||
- Preserve contest workflow speed and discoverability.
|
||||
- Do not change zoom, selection, focus, sorting, tab choice or prefilled text as an incidental side effect.
|
||||
- Map reset behaviour should clear the selected target without changing the zoom unless a new task explicitly changes this.
|
||||
- New station selection should preserve the established `/cq callsign` prefill behaviour.
|
||||
- If no send category is selected, preserve the established Main-category fallback unless explicitly changed.
|
||||
- Null/unknown data must render as unavailable/empty according to current UI conventions, not as fake zero values.
|
||||
|
||||
## WebView / map compatibility
|
||||
|
||||
- The Leaflet WebView workaround that disables problematic CSS 3D transforms before Leaflet loads is a known Java 21 stability measure. Do not remove or reorder it without reproducing and understanding the original rendering/flicker problem.
|
||||
|
||||
## Autoanswer / beacon safety
|
||||
|
||||
- Prevent automated-message loops.
|
||||
- Respect the established minimum interval/cooldown logic.
|
||||
- Do not consume a cooldown for a reply that is rejected before a complete valid TX item is queued.
|
||||
- Preserve priority of frequency/QRG requests where established.
|
||||
- Cooldown identity must not accidentally collapse unrelated callsign/category identities.
|
||||
- Treat the current implementation/tests as the source of truth for exact message markers and timer details.
|
||||
|
||||
## Build and verification
|
||||
|
||||
Use the Maven wrapper.
|
||||
|
||||
Windows:
|
||||
|
||||
```text
|
||||
.\mvnw.cmd ...
|
||||
```
|
||||
|
||||
Read the current `pom.xml` before relying on version numbers.
|
||||
|
||||
At the package creation snapshot the project uses Java 21 / JavaFX 21.x and JUnit 5/Mockito, with PMD and SpotBugs integrated.
|
||||
|
||||
Important: Maven/Surefire configuration has historically allowed test failures to be ignored, and static-analysis findings may not fail the build. Therefore:
|
||||
|
||||
- inspect the Maven test summary;
|
||||
- inspect Surefire results when needed;
|
||||
- do not infer "all tests passed" from exit code 0;
|
||||
- report PMD/SpotBugs findings that are visible in the relevant build.
|
||||
|
||||
Run focused tests first, then normally the relevant broader test/build command for the scope.
|
||||
|
||||
## Documentation and durable project context
|
||||
|
||||
Use `$software-project-context`, `$kst4contest-documentation`, and `$praktimarc-writing-style` as relevant.
|
||||
|
||||
Do not perform a full manual or website audit after every implementation.
|
||||
|
||||
After a completed change:
|
||||
|
||||
1. perform a short documentation-impact classification;
|
||||
2. if user-visible behaviour is plausibly affected, search only the relevant German and English manual sections under `github_docs/`;
|
||||
3. keep both language versions semantically aligned when an update is required;
|
||||
4. check README and website feature text only when the changed feature/configuration is represented there or is likely to need representation;
|
||||
5. identify screenshots that are likely stale instead of fabricating replacements;
|
||||
6. update `docs/PROJECT_CONTEXT.md` for significant architectural, protocol, state/persistence, operational, integration, deployment, workaround, or long-lived behavioural decisions;
|
||||
7. keep `Related Projects / Integration Points` current when KST4Contest, its website, hamradioonline infrastructure or planned propagation services affect one another.
|
||||
|
||||
A full documentation audit is reserved for explicit audit requests, major release preparation, broad UI/workflow changes, or evidence that documentation is broadly stale.
|
||||
|
||||
## Website
|
||||
|
||||
The repository contains an Eleventy-based website under `website/` with its own tests/build logic.
|
||||
|
||||
Do not assume website deployment/update-feed details; inspect current scripts/workflows before changing them.
|
||||
|
||||
## Change scope and Git
|
||||
|
||||
- No unrelated refactoring.
|
||||
- No production dependency without prior approval.
|
||||
- No automatic version bump.
|
||||
- No commit/push/merge/tag/release/deploy without separate explicit authorization.
|
||||
- Preserve deliberate test data and easter eggs unless explicitly changed.
|
||||
- Never overwrite unrelated working-tree changes.
|
||||
|
||||
## Completion
|
||||
|
||||
After implementation, report in German:
|
||||
|
||||
- understanding fulfilled;
|
||||
- changed files;
|
||||
- important design decisions;
|
||||
- tests/builds and exact results;
|
||||
- documentation-impact classification;
|
||||
- manual/website/README/context updates made or why none were necessary;
|
||||
- related-project impact when relevant;
|
||||
- remaining uncertainty;
|
||||
- suggested next action, without performing it automatically.
|
||||
@@ -0,0 +1,205 @@
|
||||
# KST4Contest Project Context
|
||||
|
||||
Last reviewed: 2026-09-02
|
||||
|
||||
This file is the durable technical project context for KST4Contest. It is not a user manual and not a replacement for the changelog. Current code, tests and authoritative external specifications remain the source of truth when this document is stale or ambiguous.
|
||||
|
||||
## Purpose
|
||||
|
||||
KST4Contest is a Java/JavaFX desktop client for ON4KST chat focused on VHF/UHF/microwave contest workflows. It combines chat handling with contest-oriented station prioritisation, sked/timeline workflows and integrations with logging, aircraft-scatter, rotor and DX-cluster tooling.
|
||||
|
||||
## Current Architecture
|
||||
|
||||
- Java 21 / JavaFX desktop application built with Maven.
|
||||
- Main code is under `src/main/java/kst4contest/`.
|
||||
- Responsibilities are separated across controller, service, logic, model, utility and view areas.
|
||||
- Network/parser/service/controller/UI boundaries should remain explicit.
|
||||
- Long-running network/message processing must tolerate malformed or incomplete external input without terminating processing threads.
|
||||
- JavaFX `ObservableList` state is a UI projection, not the canonical worker-thread domain store.
|
||||
|
||||
## Important Invariants
|
||||
|
||||
### Chat identity
|
||||
|
||||
- For remote chat participants, the complete visible callsign plus category forms the chat-member identity.
|
||||
- Full callsign variants can therefore be distinct chat-member identities.
|
||||
- Base-call normalization is permitted only for explicitly base-call-wide functions.
|
||||
- Worked status is shared across suffix variants of the same base call.
|
||||
- Monitoring a variant such as `DN9APW-2` or `DN9APW-70` intentionally monitors the base call `DN9APW`.
|
||||
- Suffixes must not be globally interpreted as a band/category/frequency.
|
||||
|
||||
### Band and availability semantics
|
||||
|
||||
- ON4KST categories 2 and 3 are the main operational categories, but unexpected category values must fail safely.
|
||||
- `NOT-QRV` overrides positive inferred band-availability hints.
|
||||
- Unknown/missing frequency, QRB, QTF or similar external data must remain unavailable rather than becoming a fabricated zero/default.
|
||||
- Features that depend on frequency should use the current/actual QRG according to current implemented rules; do not silently revert to a fixed 144 MHz default.
|
||||
- Complete digit-only frequencies use their final three digits as the kHz part and are accepted only when the resulting MHz value lies within a supported `Band` range. The same full-frequency parser is used for station names and public or directed chat messages. Relative QRG rules and bare three-digit context handling remain separate.
|
||||
|
||||
### JavaFX/threading
|
||||
|
||||
Conceptually:
|
||||
|
||||
```text
|
||||
thread-safe canonical domain state
|
||||
|
|
||||
| projection on JavaFX Application Thread
|
||||
v
|
||||
JavaFX ObservableList / UI state
|
||||
```
|
||||
|
||||
`MessageBusManagementThread` must not directly iterate or mutate UI-bound JavaFX collections. UI-visible changes should cross the controller/UI boundary and run on the JavaFX Application Thread.
|
||||
|
||||
## External Interfaces
|
||||
|
||||
Treat current implementation/tests and authoritative upstream documentation as source of truth before modifying any interface.
|
||||
|
||||
Known integration areas include:
|
||||
|
||||
- ON4KST chat;
|
||||
- AirScout;
|
||||
- UCXLog / DXLog UDP XML (`contactinfo`, `contactreplace`);
|
||||
- Win-Test UDP;
|
||||
- PSTRotator TCP;
|
||||
- DXCluster;
|
||||
- local SQLite persistence.
|
||||
|
||||
CR/LF framing, XML framing, ports/transports, callsign normalization and frequency formatting are protocol behaviour and must not be changed as incidental cleanup.
|
||||
|
||||
### Local DX Cluster output
|
||||
|
||||
- Local spots use a fixed 75-character, DXSpider-compatible payload line followed by two BEL characters and CRLF.
|
||||
- The DX callsign begins in column 27 and occupies up to 12 characters. The 30-character comment begins in column 40, and the five-character UTC time begins in column 71.
|
||||
- Spotter and frequency padding is calculated dynamically so frequencies from 50 MHz through 24 GHz do not shift the following fields.
|
||||
- Comments are padded or truncated to exactly 30 characters. Automatic AirScout comments retain the locator first and use the compact form `JO51HK AP 1m/100%;4m/75%`.
|
||||
- A DX callsign longer than 12 characters is rejected and logged rather than truncated.
|
||||
- Trigger conditions, QRG recognition and normalisation, login, keepalive, multi-client delivery and the local-only trust boundary remain separate from line formatting.
|
||||
|
||||
### Logging and Worked-state persistence
|
||||
|
||||
- The Simplelogfile interpreter reads the selected text file after connection startup and then once per minute using a fixed built-in callsign pattern.
|
||||
- Simplelogfile callsigns are normalized to base callsigns and set only the global Worked state for every active variant. They do not create per-band Worked or grid-square state.
|
||||
- Simplelogfile-derived Worked state is not persisted in SQLite. The selected file is the durable source and is read again in each application session.
|
||||
- The interpreter only adds positive runtime marks. It does not remove existing marks during the current session and does not reset automatically when a new contest starts. A database reset does not modify the file; callsigns contained in it are marked as worked again during the next periodic evaluation.
|
||||
- A missing selected file is created. Read, path and creation failures are contained so the periodic timer remains alive; successful creation triggers a one-time, non-blocking UI notice with the exact path and setup/contest checks.
|
||||
- Network-derived and manually assigned Worked, NOT-QRV and worked-grid state continues to use SQLite with its established lifetime and reset behaviour.
|
||||
- Each completed initial ON4KST user list loads one SQLite Worked/NOT-QRV snapshot. `ChatController` applies that snapshot by normalized base callsign to every new category and suffix variant before the completed category is published. The same event-driven path runs again after a reconnect; startup synchronization does not depend on a fixed-delay timer.
|
||||
- Automatic QRG updates require both an enabled source and valid incoming `RadioInfo` or Win-Test `STATUS` data. Merely enabling a source does not provide or validate a current QRG.
|
||||
- UCXLog-compatible QSO packets and Win-Test `ADDQSO` packets are converted into one validated external-QSO state. Logger-specific numeric, metre and centimetre values and Win-Test band IDs are normalised once; the resolved band is then the sole source for per-band Worked and worked-grid state.
|
||||
- A missing or unknown logger band sets only the global Worked state. Worked-grid state requires both a recognised project band and a valid locator; no band or locator is inferred. Packets without a usable callsign are discarded without terminating the listener.
|
||||
- External logger threads do not read or mutate the JavaFX user-list projection. `ChatController` applies global and per-band Worked state to every active variant of the base callsign on the JavaFX Application Thread before evaluating a band-upgrade notice.
|
||||
- The established Win-Test handling for 24, 47 and 76 GHz remains unchanged. Their Worked flags are retained, while only frequencies represented by the project `Band` model can create worked-grid state.
|
||||
|
||||
### Terrain data providers
|
||||
|
||||
- The active terrain profile provider is Open-Meteo using Copernicus GLO-90 data.
|
||||
- `OfflineDemImportService` only prepares a local directory and copies manually selected Copernicus GLO-30 GeoTIFF files into it. Importing files does not activate an offline provider or change the active calculation chain.
|
||||
|
||||
### ON4KST session and authentication
|
||||
|
||||
- One KST4Contest connection authenticates one ON4KST TCP session with one local login callsign and one password.
|
||||
- The TCP session uses one common locator for both categories; the locator is not part of authentication.
|
||||
- The primary category is part of the initial login. A distinct second category is added to the same session through ON4KST Single Sign-on; it must not create a second TCP connection or local login.
|
||||
- **Name in Chat** is a visible category-specific name field, not a login callsign or message destination. Private messages to the local station are addressed to the local login callsign.
|
||||
- The visible **Name in Chat** field, message context, QRG and beacon configuration remain category-specific.
|
||||
|
||||
### ON4KST session liveness
|
||||
|
||||
- After 90 seconds without inbound data, the application keeps the established empty CRLF heartbeat.
|
||||
- At about 180 seconds of inbound idle time, the TCP session sends one `RDXQ|<main chat id>|` probe. The probe state belongs to the session, so a two-category session still sends only one probe per idle phase.
|
||||
- Any subsequent inbound server frame confirms the probe. `DXQ` is accepted as the expected internal response and is not published as chat content.
|
||||
- If no inbound frame arrives by about 210 seconds, the existing reconnect flow remains responsible for replacing the session.
|
||||
- Probe diagnostics contain the session id, main category, opcode and timing only. They must not include credentials, complete server frames or normal chat messages.
|
||||
|
||||
## User Workflow / UI Invariants
|
||||
|
||||
- Contest operating speed and low-friction interaction are primary goals.
|
||||
- Incidental code changes must not unexpectedly change selection, focus, sorting, tab state, map zoom or prefilled text.
|
||||
- Map reset clears the selected target without changing zoom unless explicitly redesigned.
|
||||
- Station selection preserves the established `/cq callsign` prefill behaviour.
|
||||
- Sending without an explicitly selected send category preserves the established Main-category fallback unless explicitly changed.
|
||||
|
||||
## Autoanswer / Beacon
|
||||
|
||||
- Automated-message loops must be prevented.
|
||||
- Cooldown/minimum-interval rules must be preserved.
|
||||
- A reply rejected before a complete valid TX item is queued must not consume cooldown.
|
||||
- Current implementation/tests define the exact message markers and timer details.
|
||||
|
||||
## Build / Verification
|
||||
|
||||
- Use the repository Maven wrapper (`.\mvnw.cmd` on Windows).
|
||||
- The project uses Java 21 / JavaFX 21.x at this context snapshot.
|
||||
- JUnit 5/Mockito, PMD and SpotBugs are part of the verification environment.
|
||||
- Build/test configuration has historically allowed some test/static-analysis failures not to fail the process exit code. Always read actual summaries/reports.
|
||||
|
||||
## Documentation Surfaces
|
||||
|
||||
- German and English manuals under `github_docs/`.
|
||||
- Repository README.
|
||||
- Eleventy-based project website under `website/`.
|
||||
- Changelog/release communication.
|
||||
- This technical project context under `docs/PROJECT_CONTEXT.md`.
|
||||
|
||||
After implementation use targeted documentation-impact checks. Do not run a complete manual audit unless explicitly requested, release preparation is broad, or targeted checks indicate systematic drift.
|
||||
|
||||
## Website / Deployment Relationship
|
||||
|
||||
The repository contains the KST4Contest website under `website/`, published separately from the desktop application build.
|
||||
|
||||
Current website/deployment scripts and update-feed behaviour must be inspected before changes; do not rely on historical assumptions.
|
||||
|
||||
## Important Decisions and Workarounds
|
||||
|
||||
- Preserve full callsign/category identity while applying base-call normalisation only to specifically defined features.
|
||||
- Keep canonical worker-thread domain state separate from JavaFX UI projections.
|
||||
- Preserve the established JavaFX WebView/Leaflet workaround that avoids problematic CSS 3D transforms unless the original rendering/flicker issue has been reproduced and the replacement is validated.
|
||||
- Deliberate test data, comments and Easter eggs are preserved unless explicitly changed.
|
||||
|
||||
## Planned Technical Direction
|
||||
|
||||
These are planned directions, not necessarily implemented behaviour:
|
||||
|
||||
- improve propagation/path modelling using higher-resolution terrain data, including Copernicus GLO-30;
|
||||
- increase terrain/path sampling through a dedicated service/API;
|
||||
- support high-precision station locations (e.g. extended Maidenhead locators or direct GPS coordinates) while preserving compatible standard display;
|
||||
- improve terrain/Fresnel/diffraction/refraction modelling for VHF/UHF/microwave use;
|
||||
- evaluate/implement richer tropospheric/scatter models;
|
||||
- continue integration of aircraft-scatter and propagation data into reachability/contest workflows.
|
||||
|
||||
Before implementing planned items, re-check current decisions and obtain a fresh concept approval.
|
||||
|
||||
## Known Limitations / Maintenance Notes
|
||||
|
||||
- Historical project context is useful but may be stale; current code/tests win.
|
||||
- External service/API behaviour must be verified against current upstream documentation when uncertain.
|
||||
- Screenshots in manuals/website may need targeted replacement after visible UI changes; never fabricate them.
|
||||
|
||||
## Recent Significant Changes
|
||||
|
||||
### 2026-08-25 – Durable project context introduced
|
||||
|
||||
- Added a persistent technical context layer so future agents/developers can understand architecture, invariants and cross-project dependencies without replaying chat history.
|
||||
- Documentation maintenance uses targeted impact assessment rather than a full audit after every implementation.
|
||||
|
||||
## Related Projects / Integration Points
|
||||
|
||||
### KST4Contest website
|
||||
|
||||
- Source is maintained inside this repository under `website/`.
|
||||
- User-facing feature/configuration changes may require a targeted website check.
|
||||
|
||||
### hamradioonline.de
|
||||
|
||||
- Serves as the broader amateur-radio umbrella site/infrastructure context.
|
||||
- KST4Contest content/download/manual links and related knowledge content may intersect with the broader site strategy.
|
||||
|
||||
### Webserver / hosting infrastructure
|
||||
|
||||
- KST4Contest website and other hamradioonline services depend on the hosting environment.
|
||||
- Operational details should be maintained in a private infrastructure context rather than duplicated into this public project context when sensitive.
|
||||
|
||||
### Planned propagation / terrain service
|
||||
|
||||
- Intended to provide richer terrain/propagation data (including higher-resolution Copernicus GLO-30-based processing) to KST4Contest and potentially related hamradioonline tooling.
|
||||
- Interface contracts must be documented on both provider and consumer sides when they become concrete.
|
||||
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
> 🇬🇧 [English version](en-AirScout-Integration) | 🇩🇪 Du liest gerade die deutsche Version
|
||||
|
||||
AirScout (von DL2ALF) ist ein Programm zur Erkennung von Flugzeugen für den Aircraft-Scatter-Betrieb. KST4Contest ist eng mit AirScout integriert und zeigt reflektierbare Flugzeuge direkt in der Benutzerliste an.
|
||||
AirScout (von DL2ALF) berechnet Aircraft-Scatter-Gelegenheiten anhand aktueller Flugzeugpositionen. KST4Contest übernimmt diese Ergebnisse und zeigt für die jeweilige Gegenstation geeignete Flugzeuge direkt in der Benutzerliste an.
|
||||
|
||||
> **Aircraft Scatter** ermöglicht sehr weitreichende Verbindungen auf VHF und höher – auch für Stationen mit geringer Höhe über NN oder ungünstigen topografischen Verhältnissen.
|
||||
|
||||
@@ -15,9 +15,9 @@ Download von AirScout:
|
||||
|
||||
---
|
||||
|
||||
## Flugzeugdaten-Feeds (ADSB)
|
||||
## Flugzeugdaten-Feeds (ADS-B)
|
||||
|
||||
Öffentliche Flugzeugdaten-Feeds im Internet sind oft unzuverlässig und begrenzt nutzbar. Eine empfohlene Alternative bietet **OV3T (Thomas)** mit einem dedizierten ADSB-Feed-Dienst:
|
||||
Öffentliche Flugzeugdaten-Feeds im Internet sind oft unzuverlässig und nur eingeschränkt nutzbar. Eine empfohlene Alternative bietet **OV3T (Thomas)** mit einem dedizierten ADS-B-Feed-Dienst:
|
||||
|
||||
- https://airscatter.dk/
|
||||
- https://www.facebook.com/groups/825093981868542
|
||||
@@ -28,7 +28,7 @@ Für diesen Dienst ist ein Account erforderlich. Bitte eine Spende für Thomas i
|
||||
|
||||
## AirScout einrichten
|
||||
|
||||
### Schritt 1: ADSB-Feed in AirScout konfigurieren
|
||||
### Schritt 1: ADS-B-Feed in AirScout konfigurieren
|
||||
|
||||
1. AirScout starten.
|
||||
2. In den AirScout-Einstellungen den OV3T-Feed-Account eintragen (Benutzername, Passwort, URL).
|
||||
@@ -85,32 +85,40 @@ Andernfalls kann es zu fehlerhaften AP-Daten kommen.
|
||||
|
||||
## AP-Spalte in der Benutzerliste
|
||||
|
||||
Nach der Einrichtung erscheint in der Benutzerliste eine **AP-Spalte** mit bis zu zwei reflektierbaren Flugzeugen pro Station.
|
||||
Nach der Einrichtung erscheint in der Benutzerliste eine **AP-Spalte**. Sie zeigt für jede Station die Ankunftszeit und das von AirScout berechnete Reflexionspotenzial der ersten beiden geeigneten Flugzeuge.
|
||||
|
||||
Beispiel-Darstellung:
|
||||
|
||||
| Station | AP-Info |
|
||||
|---|---|
|
||||
| DF9QX | 2 Planes: 0 min / 0 min, je 100% |
|
||||
| F5DYD | 2 Planes: 14 min / 31 min, je 50% |
|
||||
| DF9QX | 0 (100 %) / 0 (100 %) |
|
||||
| F5DYD | 14 (50 %) / 31 (50 %) |
|
||||
|
||||
Die Zahl vor der Klammer gibt die verbleibenden Minuten bis zur berechneten Gelegenheit an. Die Prozentzahl beschreibt das von AirScout gemeldete Reflexionspotenzial. Sie ist keine QSO-Wahrscheinlichkeit.
|
||||
|
||||
Die AP-Informationen sind auch im **Privatnachrichten-Fenster** verfügbar.
|
||||
|
||||
Die Prozentzahl gibt das Reflexionspotenzial an (Größe des Flugzeugs, Höhe, Entfernung).
|
||||
## Einfluss auf Priority Score und Timeline
|
||||
|
||||
Mindestens ein von AirScout als erreichbar gemeldetes Flugzeug erhöht den Priority Score der Station. Eine unmittelbar bevorstehende Gelegenheit in null, einer oder zwei Minuten wird zusätzlich zeitabhängig gewichtet. AirScout ist dabei nur ein Faktor neben Worked-Status, verfügbaren Bändern, QRB, Antennenrichtung, Chat-Aktivität und Skeds.
|
||||
|
||||
Die AP- und Sked-Timeline verwendet den Priority Score zur Auswahl interessanter Stationen und ordnet die nächste geeignete Aircraft-Scatter-Gelegenheit zeitlich ein. Das Reflexionspotenzial bestimmt zusätzlich die Darstellung des AP-Symbols. Die Timeline bleibt eine Vorschau; sie garantiert kein QSO.
|
||||
|
||||
---
|
||||
|
||||
## AP-Variablen in Nachrichten
|
||||
|
||||
Die Flugzeugdaten können direkt in Nachrichten eingefügt werden:
|
||||
Die Flugzeugdaten der ausgewählten Station können direkt in Shortcuts, Snippets und andere stationsbezogene Nachrichten eingefügt werden:
|
||||
|
||||
- `FIRSTAP` → z. B. `a very big AP in 1 min`
|
||||
- `SECONDAP` → z. B. `Next big AP in 9 min`
|
||||
|
||||
Details: [Makros und Variablen](Makros-und-Variablen#variablen)
|
||||
Details: [Makros und Variablen](de-Makros-und-Variablen#variablen)
|
||||
|
||||
Da die Werte eine ausgewählte Gegenstation benötigen, stehen `FIRSTAP` und `SECONDAP` nicht als globale Beacon-Variablen zur Verfügung.
|
||||
|
||||
---
|
||||
|
||||
## „Show Path in AirScout"-Button
|
||||
|
||||
In der Benutzerliste gibt es einen Button mit einem Pfeil, der die Richtung (QTF) zur ausgewählten Station anzeigt. Ein Klick maximiert AirScout und zeigt den Pfad mit reflektierbaren Flugzeugen zum ausgewählten Gesprächspartner.
|
||||
In der Benutzerliste gibt es einen Button mit einem Pfeil, der die Richtung (QTF) zur ausgewählten Station anzeigt. Ein Klick maximiert das externe AirScout-Fenster und lässt dort den Pfad zur ausgewählten Gegenstation mit den berechneten Aircraft-Scatter-Gelegenheiten anzeigen. Die Schaltfläche startet keine eigene Gelände- oder Ausbreitungsberechnung in KST4Contest.
|
||||
|
||||
@@ -4,11 +4,20 @@
|
||||
|
||||
## Verbinden mit dem Chat
|
||||
|
||||
1. Im Einstellungsfenster eine **Chat-Kategorie** auswählen (z. B. 144 MHz VHF, 432 MHz UHF, …).
|
||||
2. **Connect**-Button klicken.
|
||||
3. Warten bis die Verbindung aufgebaut ist.
|
||||
Vor dem ersten Verbindungsaufbau müssen im Einstellungsfenster das lokale Login-Rufzeichen und Passwort, der gemeinsame Locator der TCP-Sitzung sowie die primäre Chat-Kategorie konfiguriert werden. Soll zusätzlich eine zweite Kategorie verwendet werden, wird sie dort aktiviert und ausgewählt. KST4Contest meldet sich einmal an und ergänzt die zweite Kategorie per Single Sign-on innerhalb derselben TCP-Sitzung.
|
||||
|
||||
> Trennen und Neu-Verbinden ist nur über das Einstellungsfenster möglich. Es empfiehlt sich daher, das Einstellungsfenster geöffnet zu lassen.
|
||||
Die Verbindung kann auf zwei Wegen aufgebaut werden:
|
||||
|
||||
- Mit **Connect to …** im Einstellungsfenster werden die dort eingetragenen Werte übernommen und die Verbindung gestartet.
|
||||
- **File → Connect to …** verwendet die bereits in KST4Contest übernommenen Einstellungen.
|
||||
|
||||
Geänderte Einstellungen müssen mit **Save Settings** gespeichert werden, wenn sie auch beim nächsten Programmstart verwendet werden sollen.
|
||||
|
||||
Eine bestehende Verbindung kann über **File → Disconnect** oder über **Disconnect** im Einstellungsfenster beendet werden. **Exit + disconnect** beendet zusätzlich das Programm.
|
||||
|
||||
Bei einem unerwarteten Verbindungsverlust versucht KST4Contest nach einer begrenzten Wartezeit, die ON4KST-Verbindung kontrolliert neu aufzubauen. Ein fehlgeschlagener Erstaufbau blockiert die Benutzeroberfläche nicht mehr.
|
||||
|
||||
Ob die Verbindung lediglich als TCP-Verbindung besteht oder bereits vollständig angemeldet und synchronisiert ist, zeigt der [`LINK`-Status](#statusleiste-und-hinweise) im Hauptfenster.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,9 +25,48 @@
|
||||
|
||||
Das Hauptfenster besteht aus mehreren Bereichen:
|
||||
|
||||
|
||||
### Statusleiste und Hinweise
|
||||
|
||||
Die Statusleiste befindet sich am oberen Rand des Hauptfensters neben dem Menü.
|
||||
|
||||

|
||||
|
||||
Der dauerhaft sichtbare `LINK`-Indikator zeigt den tatsächlichen Zustand der ON4KST-Verbindung:
|
||||
|
||||
| Anzeige | Bedeutung |
|
||||
|---|---|
|
||||
| grünes `LINK` | Login und Synchronisation der konfigurierten Chat-Kategorien sind vollständig abgeschlossen |
|
||||
| gelbes `LINK…` | Verbindung, Anmeldung, Benutzerlistensynchronisation oder kontrolliertes Beenden läuft |
|
||||
| rotes `LINK!` | keine Verbindung oder Wartezeit vor einem automatischen Neuaufbau |
|
||||
|
||||
Der Tooltip enthält den internen Verbindungsstatus und eine genauere Beschreibung des aktuellen Schritts. Der Indikator ist keine Schaltfläche.
|
||||
|
||||
`ONLINE` wird erst gemeldet, nachdem die Anmeldung bestätigt und die Benutzerlisten der konfigurierten Kategorien vollständig empfangen wurden. Während die Verbindung noch aufgebaut oder neu synchronisiert wird, bleiben Sendfeld und **TX** deaktiviert.
|
||||
|
||||
Bei bestimmten Ereignissen erscheinen vorübergehend weitere Hinweise:
|
||||
|
||||
- `SKED` weist auf eine fällige Sked-Erinnerung hin. Der Text enthält das vollständige Zielrufzeichen und die verbleibende Zeit.
|
||||
- `BAND+` erscheint nach einem Logeintrag, wenn für die gearbeitete Station noch mindestens ein gemeinsames, aktiviertes und nicht gearbeitetes Band erkannt wurde.
|
||||
|
||||
Beide Hinweise blinken ungefähr zwölf Sekunden und verschwinden anschließend wieder. Der vollständige Inhalt beziehungsweise die Herleitung steht im jeweiligen Tooltip. Die Anzeigen sind nicht anklickbar.
|
||||
|
||||
|
||||
### PM-Fenster (oben links)
|
||||
|
||||
Zeigt alle empfangenen **Privatnachrichten** sowie abgefangene öffentliche Nachrichten, die das eigene Rufzeichen enthalten. Neue Nachrichten erscheinen in **Rot** und faden alle 30 Sekunden über Gelb bis Weiß ab.
|
||||
Das PM-Fenster zeigt die an das lokale Login-Rufzeichen adressierten Privatnachrichten und die zugehörigen ausgehenden Antworten.
|
||||
|
||||
Nicht selbst gesendete Nachrichten erscheinen dort zusätzlich, wenn ihr Text ohne Beachtung der Groß- und Kleinschreibung das konfigurierte eigene Login-Rufzeichen enthält. Das gilt für öffentliche Nachrichten an `ALL` ebenso wie für gerichtete Nachrichten zwischen anderen Chatteilnehmern. Gerade im zweiten Fall lässt sich dieses PM-Catching flapsig als **„Lästererkennung“** bezeichnen.
|
||||
|
||||
Der tatsächliche Empfänger, der Nachrichtentext, die Chat-Kategorie und das Routing bleiben unverändert. Die Nachricht erhält lediglich eine zusätzliche Darstellung im PM-Fenster.
|
||||
|
||||
Ist das [QSO-Monitoring](de-Funktionen#qso-monitoring-ab-v131) aktiviert, erscheinen dort zusätzlich die erfassten Nachrichten der überwachten Basisrufzeichen. Diese Einträge erhalten eine `Sniffed:`-Kennzeichnung mit dem vollständigen sichtbaren Absender und Empfänger.
|
||||
|
||||
Neue, nicht selbst gesendete Zeilen durchlaufen sechs grüne Altersstufen und kehren nach fünf Minuten zur normalen Tabellenfarbe zurück. Eigene Nachrichten behalten ihre separate Hervorhebung. Die farbliche Darstellung ist nur ein zeitlicher Hinweis; sie verändert weder Inhalt noch Routing der Nachricht.
|
||||
|
||||
Die Auswahl einer eingehenden Zeile bereitet eine Antwort an den Absender vor. Bei einer eigenen ausgehenden Nachricht wird stattdessen der ursprüngliche Empfänger als Nachrichtenziel wiederhergestellt. Caught- und Monitoring-Zeilen lösen keine PM-Audioausgabe aus.
|
||||
|
||||
Altersstufen: [Farbige PM-Zeilen](de-Funktionen#farbige-pm-zeilen-ab-v125). Erkennung und Grenzen: [PM-Abfang](de-Funktionen#pm-abfang-catching-personal-messages-ab-v11).
|
||||
|
||||
### Benutzerliste (Chat Members)
|
||||
|
||||
@@ -60,15 +108,47 @@ Jede Statuszelle besitzt einen Tooltip mit der Legende und dem für die betreffe
|
||||
Ein grün und fett dargestelltes Rufzeichen kennzeichnet eine aus einer gerichteten Nachricht hergeleitete Richtungsgelegenheit. Die Markierung bezieht sich auf den Absender der Nachricht und bleibt höchstens fünf Minuten sichtbar. Herleitung und Grenzen: [Richtungsgelegenheiten aus gerichteten Nachrichten](de-Funktionen#richtungsgelegenheiten-aus-gerichteten-nachrichten).
|
||||
### Sendfeld
|
||||
|
||||
Texteingabe für ausgehende Nachrichten. Nach Klick auf ein Rufzeichen in der Benutzerliste erhält das Sendfeld automatisch den Fokus – sofort tippen ohne Doppelklick (ab v1.22).
|
||||
Das Sendfeld enthält den vorbereiteten Text für die nächste ausgehende Nachricht.
|
||||
|
||||
### MYQRG-Feld
|
||||
Wird eine Station bewusst per Maus oder Tastatur in der Benutzerliste ausgewählt, bereitet KST4Contest eine gerichtete Nachricht vor:
|
||||
|
||||
Rechts neben dem Sendbutton. Zeigt die aktuelle eigene QRG an, kann auch manuell eingetragen werden.
|
||||
```text
|
||||
/cq RUFZEICHEN
|
||||
```
|
||||
|
||||
### MYQTF-Feld *(für v1.3)*
|
||||
Dabei werden das vollständige sichtbare Rufzeichen einschließlich eines vorhandenen Suffixes und die Chat-Kategorie der ausgewählten Station beibehalten. Ein Ziel wie `9A0BB-70` wird nicht auf `9A0BB` verkürzt.
|
||||
|
||||
Eingabefeld für die aktuelle Antennenrichtung. Wird für die geplante `MYQTF`-Variable verwendet.
|
||||
Eine Hintergrundaktualisierung, Neusortierung oder Filteränderung darf einen bereits bearbeiteten Nachrichtentext nicht überschreiben. Nur eine tatsächliche Auswahl durch den Operator bereitet den `/cq`-Empfänger erneut vor.
|
||||
|
||||
- **TX** oder `Enter` sendet den vorbereiteten Text.
|
||||
- `Esc` leert das Sendfeld.
|
||||
- Während KST4Contest nicht vollständig mit ON4KST verbunden ist, bleiben Sendfeld und **TX** deaktiviert.
|
||||
|
||||
Shortcuts, Snippets und Variablen sind unter [Makros und Variablen](de-Makros-und-Variablen) beschrieben.
|
||||
|
||||
### MYQRG- und SECONDQRG-Feld
|
||||
|
||||
Die beiden QRG-Felder enthalten die eigenen Frequenzen der primären und sekundären Chat-Kategorie.
|
||||
|
||||
`MYQRG` kann von einer aktivierten TRX-Synchronisation aktualisiert oder – bei deaktivierten automatischen QRG-Quellen – von Hand eingetragen werden. `SECONDQRG` bleibt davon unabhängig und enthält die QRG der zweiten Kategorie.
|
||||
|
||||
Die Auswahl einer Station aus dem zweiten Chat verändert die Bedeutung der beiden Werte nicht: `MYQRG` gehört weiterhin zur primären, `SECONDQRG` zur sekundären Kategorie.
|
||||
|
||||
Weitere Einzelheiten: [TRX-Sync-Einstellungen](de-Konfiguration#trx-sync-einstellungen).
|
||||
|
||||
### MYQTF-Feld
|
||||
|
||||
Das MYQTF-Feld zeigt die aktuelle Antennenrichtung als numerischen Winkel in Grad.
|
||||
|
||||
Ist PSTRotator aktiviert, wird der Wert automatisch übernommen und das Feld ist nicht manuell bearbeitbar. Ohne aktive Rotatorsynchronisation kann die Antennenrichtung direkt eingetragen werden. Die Änderung wird beim Verlassen des Feldes übernommen.
|
||||
|
||||
Der Wert beeinflusst unter anderem:
|
||||
|
||||
- die QTF-Filterung,
|
||||
- die Darstellung des Antennensektors auf der Stationskarte,
|
||||
- die Prioritätsberechnung,
|
||||
- die AP-Timeline und
|
||||
- die Variable `MYQTF`.
|
||||
|
||||
---
|
||||
|
||||
@@ -86,27 +166,77 @@ Damit muss der Divider nicht allein deshalb verschoben werden, um eine einzelne
|
||||
|
||||
---
|
||||
|
||||
## Filter
|
||||
## Filter und Reachability-Steuerung
|
||||
|
||||
Die Filterleiste befindet sich oberhalb der Chatmember-Tabelle. Sie ist in mehrere logisch zusammengehörige Bereiche gegliedert:
|
||||
|
||||
- **Show only QTF** begrenzt die Liste auf eine gewählte Antennenrichtung.
|
||||
- **Show only QRB [km] <=** setzt eine maximale Entfernung.
|
||||
- **Find** sucht nach einem Rufzeichen.
|
||||
- **wkd** blendet Rufzeichen aus, die bereits auf mindestens einem Band gearbeitet wurden.
|
||||
- Die einzelnen Band-Schaltflächen blenden eine Station aus, wenn sie auf dem betreffenden Band bereits gearbeitet oder dort als NOT QRV markiert wurde. Angezeigt werden nur die für die eigene Station aktivierten Bänder.
|
||||
- **Only new grids** zeigt ausschließlich Stationen aus vierstelligen Großfeldern, die auf noch keinem Band gearbeitet wurden.
|
||||
- **Grid color** ist kein Filter. Die Funktion markiert das QRA-Feld bereits gearbeiteter Großfelder, ohne Stationen auszublenden.
|
||||
- **New bands** zeigt Stationen mit mindestens einer erkannten, an der eigenen Station aktivierten und noch nicht gearbeiteten Bandmöglichkeit. NOT-QRV-Markierungen haben Vorrang.
|
||||
- **Reachability**, **Tropo >=0dB** und **AS next 5m** schränken die Liste anhand der gewählten Strecken- beziehungsweise AirScout-Bedingungen ein.
|
||||
|
||||
Die Filterleiste besitzt keine feste Breite. QTF sowie die Worked- und Reachability-Filter nutzen zunächst den gesamten Platz ihrer jeweiligen Zeile. Wird der horizontale Divider nach rechts verschoben und die Chatmember-Ansicht dadurch schmaler, wechseln die Controls erst dann in die nächste Zeile, wenn ihre tatsächlich benötigte Breite nicht mehr zur Verfügung steht.
|
||||
Die Filterleiste befindet sich oberhalb der Chatmember-Tabelle. Filter können miteinander kombiniert werden; eine Station bleibt nur sichtbar, wenn sie alle aktiven Bedingungen erfüllt.
|
||||
|
||||

|
||||
|
||||
Im Klartext: Die Filter bestimmen weiterhin den Inhalt der Tabelle, aber nicht mehr die Mindestbreite der gesamten rechten Programmseite. In der normalen Ansicht bleibt die Leiste kompakt. Erst bei einer tatsächlich schmalen Ansicht benötigt sie mehr Höhe. Der Divider kann anschließend wieder nach links verschoben werden; die Controls ordnen sich unmittelbar neu an.
|
||||
### Stationsfilter
|
||||
|
||||
| Bedienelement | Wirkung |
|
||||
|---|---|
|
||||
| **Show only QTF** | Zeigt nur Stationen innerhalb der gewählten Antennenrichtung und des konfigurierten Öffnungswinkels |
|
||||
| **Show only QRB [km] <=** | Begrenzt die Liste auf die eingetragene maximale Entfernung |
|
||||
| **Find** | Filtert nach einem vollständigen oder teilweisen Rufzeichen |
|
||||
| **wkd** | Blendet Basisrufzeichen aus, die bereits auf mindestens einem unterstützten Band gearbeitet wurden |
|
||||
| einzelne Band-Schaltflächen | Blenden Stationen aus, die auf dem betreffenden Band bereits gearbeitet oder dort als NOT QRV markiert wurden |
|
||||
| **Inactive stations** | Blendet Stationen aus, deren letzte Chataktivität mehr als 20 Minuten zurückliegt |
|
||||
| **Only new grids** | Zeigt nur Stationen aus vierstelligen Großfeldern, die bisher auf keinem Band gearbeitet wurden |
|
||||
| **New bands** | Zeigt Stationen mit mindestens einer erkannten, lokal aktivierten und noch nicht gearbeiteten Bandmöglichkeit |
|
||||
| **Tropo >=0dB** | Zeigt Stationen mit einer berechneten, nicht negativen SSB-Marge |
|
||||
| **AS next 5m** | Zeigt Stationen mit einem aktuellen oder innerhalb der nächsten fünf Minuten erwarteten AirScout-Fenster |
|
||||
|
||||
Bei **New bands** werden aktuelle QRGs, Bandangaben im Namensfeld und aktive Rufzeichenvarianten gemeinsam ausgewertet. Manuelle NOT-QRV-Markierungen haben Vorrang.
|
||||
|
||||
Der Filter **Tropo >=0dB** entfernt nur Stationen, für die eine abgeschlossene Berechnung eine negative Marge ergeben hat. Noch nicht berechnete oder fehlgeschlagene Auswertungen bleiben sichtbar. Andernfalls würde ein fehlender API-Wert wie ein nachgewiesen ungeeigneter Funkweg behandelt.
|
||||
|
||||
### Grid color
|
||||
|
||||
**Grid color** ist kein Filter. Die Funktion verändert ausschließlich die Darstellung des QRA-Feldes und kennzeichnet bereits gearbeitete vierstellige Großfelder.
|
||||
|
||||
Die Station bleibt unabhängig von der Farbmarkierung in der Tabelle sichtbar. **Reset filters** deaktiviert diese Anzeige deshalb nicht.
|
||||
|
||||
### Reachability und Calc selected
|
||||
|
||||
Das Dropdown **Reachability** bestimmt das Band, auf das sich die Tropo-Spalte, der Tropo-Filter und eine ausdrücklich gestartete Streckenberechnung beziehen.
|
||||
|
||||
- **Auto** leitet das Band aus der aktuellen Stations-QRG, Bandangaben im Namensfeld und der unterstützten Chat-Kategorie her.
|
||||
- Ein ausdrücklich gewähltes Band übersteuert diese automatische Auswahl für die Reachability-Auswertung.
|
||||
|
||||
Eine Änderung des Dropdowns startet keine Berechnung für die gesamte Benutzerliste. Das wäre bei einer Online-Höhendatenquelle unnötig langsam und würde externe API-Abfragen vervielfachen.
|
||||
|
||||
**Calc selected** berechnet ausschließlich die aktuell ausgewählte Station auf dem gewählten beziehungsweise automatisch hergeleiteten Band. Das Ergebnis wird anschließend in der Tropo-Spalte und den zugehörigen Ansichten verwendet.
|
||||
|
||||
### Filter zurücksetzen
|
||||
|
||||
**Reset filters** entfernt:
|
||||
|
||||
- den QTF-Filter,
|
||||
- den QRB-Filter,
|
||||
- den Inhalt des Rufzeichen-Suchfeldes,
|
||||
- alle Worked- und Bandfilter,
|
||||
- **Inactive stations**,
|
||||
- **Only new grids**,
|
||||
- **New bands**,
|
||||
- **Tropo >=0dB** und
|
||||
- **AS next 5m**.
|
||||
|
||||
Die internen Filterprädikate werden dabei ausdrücklich geleert. Es genügt nicht, lediglich die sichtbaren Toggle-Buttons zurückzusetzen.
|
||||
|
||||
Nicht verändert werden:
|
||||
|
||||
- **Grid color**, weil es sich um eine Darstellungsoption handelt, und
|
||||
- die Auswahl im **Reachability**-Dropdown, weil sie das Berechnungsband festlegt und nicht unmittelbar die Tabelle filtert.
|
||||
|
||||
### Verhalten bei schmaler Ansicht
|
||||
|
||||
Die Filterleiste besitzt keine feste Breite. QTF-, Worked- und Reachability-Controls nutzen zunächst den verfügbaren Platz ihrer jeweiligen Zeile.
|
||||
|
||||
Wird der mittlere Divider nach rechts verschoben und die Chatmember-Ansicht dadurch schmaler, wechseln Bedienelemente erst dann in die nächste Zeile, wenn ihre tatsächlich benötigte Breite nicht mehr ausreicht. Wird der Bereich wieder breiter, ordnen sie sich unmittelbar neu an.
|
||||
|
||||
Im Klartext: Die Filter bestimmen den Tabelleninhalt, aber nicht mehr die Mindestbreite der gesamten rechten Programmseite.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
## Stationsinfo-Panel (Further Info)
|
||||
@@ -182,28 +312,67 @@ Herleitung und Grenzen: [Prioritätsscore und Prioritätsliste](de-Funktionen#pr
|
||||
Die Stationskarte kann auf zwei Wegen geöffnet werden:
|
||||
|
||||
- **Windows → Show / hide station map** öffnet oder schließt das Kartenfenster.
|
||||
- **Show on map** im **Further Info**-Bereich öffnet die Karte und zentriert sie auf die ausgewählte Station.
|
||||
- **Show on map** im **Further Info**-Bereich öffnet die Karte und fokussiert die ausgewählte Station.
|
||||
|
||||
Die Karte zeigt die Stationen, die auch nach Anwendung der aktuellen Benutzerlistenfilter noch sichtbar sind. Ein Hinweis in der Kopfzeile zeigt an, wenn eine gefilterte Ansicht aktiv ist.
|
||||
Die Karte verwendet die Stationen, die nach Anwendung der aktuellen Benutzerlistenfilter noch sichtbar sind. Die Kopfzeile zeigt die Anzahl der dargestellten Stationen und weist mit `filtered view active` auf eine gefilterte Ansicht hin.
|
||||
|
||||

|
||||
|
||||
Ein einzelner Stationsmarker kann direkt angeklickt werden. KST4Contest übernimmt die Station daraufhin als aktuelle Auswahl, scrollt die Benutzerliste zum passenden Chatmember und aktualisiert den **Further Info**-Bereich.
|
||||
### Station auswählen
|
||||
|
||||
Marker, die bei der aktuellen Zoomstufe zu dicht beieinanderliegen, werden als Cluster mit einer Stationsanzahl angezeigt. Ein Klick auf einen Cluster vergrößert den betreffenden Kartenausschnitt. Erst ein anschließend sichtbarer einzelner Marker wählt eine konkrete Station aus.
|
||||
Ein einzelner Stationsmarker kann direkt angeklickt werden. KST4Contest:
|
||||
|
||||
Für die ausgewählte Station erscheinen rechts unter **Selected station**:
|
||||
1. übernimmt den konkreten Chatmember als aktuelle Auswahl,
|
||||
2. scrollt die Benutzerliste zum entsprechenden Eintrag,
|
||||
3. aktualisiert den **Further Info**-Bereich und
|
||||
4. bereitet das vollständige sichtbare Rufzeichen als `/cq`-Empfänger vor.
|
||||
|
||||
- Rufzeichen,
|
||||
Marker, die bei der aktuellen Zoomstufe zu dicht beieinanderliegen, werden als Cluster mit einer Stationsanzahl dargestellt. Ein Klick auf einen Cluster vergrößert den betreffenden Kartenausschnitt. Erst ein anschließend sichtbarer einzelner Marker wählt eine konkrete Station aus.
|
||||
|
||||
Die Kopfzeile ergänzt bei ausgewählter Station:
|
||||
|
||||
- vollständiges Rufzeichen,
|
||||
- Locator,
|
||||
- QRB und QTF,
|
||||
- erkannte aktive Bänder,
|
||||
- gegebenenfalls `B+` für eine offene Bandmöglichkeit und
|
||||
- eine gegebenenfalls vorhandene `B+`-Bandmöglichkeit und
|
||||
- die zuletzt bekannten QRGs.
|
||||
|
||||
**Trigger cluster spot** sendet für die ausgewählte Station einen einzelnen Spot an die mit dem integrierten DX-Cluster-Server verbundenen Logprogramme. Die Schaltfläche setzt deshalb einen aktivierten Cluster-Server und mindestens einen verbundenen Client voraus.
|
||||
Lange Inhalte werden in der Kopfzeile gekürzt. Der vollständige Text steht im Tooltip.
|
||||
|
||||
Unterhalb der Karte befindet sich das Höhenprofil. Rechts werden die dazugehörigen Detailwerte angezeigt, unter anderem:
|
||||
### Auswahl mit Reset view löschen
|
||||
|
||||
**Reset view** löscht die Stationsauswahl, ohne die Kartenposition oder den Zoomlevel zu verändern.
|
||||
|
||||
Dabei werden:
|
||||
|
||||
- die ausgewählte Station zurückgesetzt,
|
||||
- die Auswahl in der Benutzerliste aufgehoben,
|
||||
- die Verbindungslinie zur Gegenstation entfernt,
|
||||
- eine noch laufende Auswertung der vorherigen Station verworfen und
|
||||
- der rechte Analysebereich entfernt.
|
||||
|
||||
Die Karte selbst bleibt im zuvor gewählten Ausschnitt. Die Funktion ist deshalb kein geografischer Reset auf den eigenen Standort.
|
||||
|
||||

|
||||
|
||||
Wird anschließend wieder ein einzelner Marker gewählt, erscheinen Stationsauswahl und Analysebereich erneut.
|
||||
|
||||
### DX-Cluster-Spot auslösen
|
||||
|
||||
**Trigger cluster spot** erscheint nur bei ausgewählter Station. Die Schaltfläche sendet einen einzelnen Spot an die mit dem integrierten DX-Cluster-Server verbundenen Logprogramme.
|
||||
|
||||
Vorausgesetzt werden:
|
||||
|
||||
- ein aktivierter lokaler DX-Cluster-Server,
|
||||
- mindestens ein verbundener Cluster-Client und
|
||||
- eine für die ausgewählte Station verwendbare QRG.
|
||||
|
||||
Der Spot wird nicht an einen öffentlichen Internet-Cluster gesendet.
|
||||
|
||||
### Streckenanalyse
|
||||
|
||||
Unterhalb der Karte befindet sich das Höhenprofil. Der rechte Analysebereich zeigt unter anderem:
|
||||
|
||||
- verwendete Datenquelle und Anzahl der Höhenpunkte,
|
||||
- Analysefrequenz,
|
||||
@@ -212,18 +381,28 @@ Unterhalb der Karte befindet sich das Höhenprofil. Rechts werden die dazugehör
|
||||
- Fresnel-Freiheit,
|
||||
- erkannte Hindernisse,
|
||||
- Link-Budget,
|
||||
- geschätzter Empfangspegel und
|
||||
- geschätzten Empfangspegel und
|
||||
- eine zusammenfassende Pfadbewertung.
|
||||
|
||||
Mit **Hide path analysis** werden das Profil unterhalb der Karte und die ausführlichen Analysewerte rechts gemeinsam ausgeblendet. Der Kartenbereich erhält dadurch mehr Platz.
|
||||
Die Auswertung verwendet dasselbe zentral hergeleitete Band wie die Reachability-Funktionen. Ein im **Reachability**-Dropdown ausdrücklich gewähltes Band wird berücksichtigt.
|
||||
|
||||
Die Werte bleiben technische Abschätzungen. Gebäude, Bewuchs, lokale Abschattungen, aktuelle Ausbreitungsbedingungen und nicht bekannte Stationsparameter können das reale Ergebnis deutlich verändern.
|
||||
|
||||
### Streckenanalyse ausblenden
|
||||
|
||||
Mit **Hide path analysis** werden Höhenprofil und rechter Analysebereich gemeinsam ausgeblendet. Der Kartenbereich erhält dadurch mehr Platz.
|
||||
|
||||

|
||||
|
||||
Der Hinweis **Path analysis is hidden** bleibt zusammen mit **Show path analysis** sichtbar. Die Funktion kann daher ohne Umweg wieder eingeschaltet werden. Der Zustand wird gespeichert.
|
||||
Der Hinweis **Path analysis is hidden** und die Schaltfläche **Show path analysis** bleiben sichtbar. Die Funktion kann daher ohne Umweg wieder eingeschaltet werden.
|
||||
|
||||
Der Divider zwischen Karte und Detailbereich lässt sich horizontal verschieben. Bei schmalem Detailbereich werden längere Angaben umgebrochen; falls die Höhe nicht ausreicht, erscheint dort eine vertikale Scrollleiste.
|
||||
Ist beim Wiedereinblenden keine Station ausgewählt, erscheint kein leerer rechter Bereich. Er wird erst wieder aufgebaut, nachdem eine konkrete Station gewählt wurde.
|
||||
|
||||
Ausführliche Herleitung und Grenzen: [Stationskarte und Streckenanalyse](de-Funktionen#stationskarte-und-streckenanalyse-ab-v141)
|
||||
Die Auswahl wird gespeichert und beim nächsten Programmstart wiederhergestellt.
|
||||
|
||||
Der Divider zwischen Karte und Detailbereich lässt sich horizontal verschieben. Bei einem schmalen Detailbereich werden längere Angaben umgebrochen; reicht die verfügbare Höhe nicht aus, erscheint eine vertikale Scrollleiste.
|
||||
|
||||
Ausführliche Herleitung und Grenzen: [Stationskarte und Streckenanalyse](de-Funktionen#stationskarte-und-streckenanalyse-ab-v141).
|
||||
|
||||
---
|
||||
|
||||
@@ -270,12 +449,29 @@ Herleitung und Grenzen: [Globale Nachrichtenansichten](de-Funktionen#globale-nac
|
||||
|
||||
## Menü
|
||||
|
||||
### File
|
||||
|
||||
- **Connect to …** startet die Verbindung mit den bereits übernommenen Einstellungen.
|
||||
- **Disconnect** beendet die aktuelle ON4KST-Verbindung, ohne KST4Contest zu schließen.
|
||||
- **Exit + disconnect** beendet die Verbindung und anschließend das Programm.
|
||||
|
||||
Die Connect- und Disconnect-Einträge werden entsprechend dem aktuellen Verbindungszustand aktiviert oder deaktiviert.
|
||||
|
||||
### Options
|
||||
|
||||
- **Set QRG as name in Chat (main category)** sendet `/SETNAME` mit der aktuellen `MYQRG` an die primäre Chat-Kategorie.
|
||||
- **Show me as away in chat** sendet `/AWAY`.
|
||||
- **Show me as active in chat** sendet `/BACK`.
|
||||
- **Show options** blendet das Einstellungsfenster ein beziehungsweise aus.
|
||||
|
||||
Die serverbezogenen Funktionen sind nur bei vollständig aufgebauter ON4KST-Verbindung verfügbar.
|
||||
|
||||
### Windows
|
||||
|
||||
- **Hide cluster / stranger QSOs** beziehungsweise **Show cluster / stranger QSOs**: Blendet das zusätzliche Cluster- und QSO-Monitorfenster aus oder wieder ein.
|
||||
- **hide options** beziehungsweise **show options**: Blendet das Einstellungsfenster aus oder wieder ein.
|
||||
- **Use dark mode design**: Aktiviert das dunkle Farbschema.
|
||||
- **Use default mode design**: Aktiviert das normale helle Farbschema.
|
||||
- **Hide cluster / stranger QSOs** beziehungsweise **Show cluster / stranger QSOs** blendet das zusätzliche Cluster- und QSO-Monitorfenster aus oder wieder ein.
|
||||
- **hide options** beziehungsweise **show options** blendet das Einstellungsfenster aus oder wieder ein.
|
||||
- **Use dark mode design** aktiviert das dunkle Farbschema.
|
||||
- **Use default mode design** aktiviert das normale helle Farbschema.
|
||||
- **Show / hide station map** öffnet beziehungsweise schließt das separate Fenster mit Stationskarte und Streckenanalyse.
|
||||
|
||||
---
|
||||
|
||||
@@ -8,15 +8,16 @@ Die veröffentlichten Stable-Versionen und ihre Programmpakete stehen unter [Git
|
||||
|
||||
---
|
||||
|
||||
## v1.42 – Nightly / in Entwicklung
|
||||
## v1.42.0 (2026-08-22)
|
||||
|
||||
> Stand dieses Abschnitts: 10. August 2026.
|
||||
> v1.42 ist noch kein veröffentlichtes Stable-Release. Bis zur Freigabe können weitere Änderungen hinzukommen.
|
||||
**Gemeinsamer Bandkontext, sitzungsbasierte ON4KST-Verbindung und signierte macOS-Pakete**
|
||||
|
||||
v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen, Worked-Status, NOT-QRV-Markierungen, Rufzeichensuffixe und Frequenzen werden dadurch konsistenter in der Benutzerliste, der Stationskarte, der Prioritätsberechnung und den externen Schnittstellen verwendet.
|
||||
|
||||
### Neu
|
||||
|
||||
- **Sichtbarer ON4KST-Verbindungsstatus:** Ein kompakter `LINK`-Indicator im Hauptfenster zeigt den tatsächlichen Zustand der ON4KST-Verbindung an. Grün bedeutet vollständig angemeldet und synchronisiert, Gelb kennzeichnet Verbindungsaufbau und Synchronisation, Rot eine unterbrochene Verbindung, einen Fehler oder die Wartezeit vor dem nächsten Verbindungsversuch.
|
||||
|
||||
- **Gemeinsame Herleitung verfügbarer Bänder:** Ein zentraler `BandOpportunityResolver` wertet aktuelle QRGs, Bandangaben im Namensfeld, aktive Rufzeichenvarianten, Worked-Informationen und NOT-QRV-Markierungen gemeinsam aus. Benutzerliste, **New bands**, Band-Upgrade-Hinweis, Priority Score, Stationskarte und automatische Bandauswahl verwenden damit dieselbe Grundlage.
|
||||
|
||||
- **Erweiterte Bandanzeige:** Die Bandspalten unterscheiden jetzt:
|
||||
@@ -33,6 +34,8 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
|
||||
|
||||
- **Manuelle QTF-Eingabe:** Die aktuelle Antennenrichtung kann auch ohne PSTRotator direkt in KST4Contest geändert werden.
|
||||
|
||||
- **Nachrichtenvariable `MYQTF`:** Die aktuelle Antennenrichtung kann als numerischer Winkel in Grad in Shortcuts, Snippets und andere unterstützte Nachrichtentexte eingesetzt werden.
|
||||
|
||||
- **Filter zurücksetzen:** Ein eigener Reset-Button entfernt die aktiven Filterprädikate der Benutzerliste zuverlässig.
|
||||
|
||||
- **Kartencluster:** Räumlich dicht beieinanderliegende Stationen werden bei niedrigen Zoomstufen zusammengefasst. Die ausgewählte Station und relevante Richtungsgelegenheiten bleiben einzeln sichtbar.
|
||||
@@ -41,7 +44,13 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
|
||||
|
||||
### Geändert
|
||||
|
||||
- **QRG-Erkennung präzisiert:** Vollständige und relative Frequenzangaben werden weiterhin erkannt. Nackte dreistellige Zahlen gelten nur noch bei erkennbarem Frequenzkontext als QRG. Signalrapporte, Bandangaben und andere Zahlen erzeugen dadurch seltener falsche Frequenzen.
|
||||
- **DX-Cluster-Zeilenformat vereinheitlicht:** Lokale Spots verwenden jetzt eine feste, DXSpider-kompatible 75-Zeichen-Nutzzeile mit dem DX-Rufzeichen ab Spalte 27, einem 30 Zeichen breiten Kommentarfeld und der UTC-Zeit ab Spalte 71. Das Format bleibt auch bei Mikrowellenfrequenzen bis 24 GHz stabil. Überlange DX-Rufzeichen werden verworfen und protokolliert, statt still abgeschnitten zu werden; AirScout- und Testkommentare sind entsprechend kompakter.
|
||||
|
||||
- **Sessionbezogene ON4KST-Verbindungssteuerung:** Socket, Reader, Writer, Messagebus und Warteschlangen gehören jetzt zu einer eindeutig identifizierten Verbindungssession. Veraltete Threads einer abgelösten Verbindung können dadurch keine Daten mehr verarbeiten oder die neue Verbindung schließen. `ONLINE` wird erst nach bestätigtem Login und vollständig empfangenen Benutzerlisten gemeldet. Verbindungsaufbau, Login und Synchronisation besitzen feste Zeitlimits; Heartbeats, ausbleibende Eingangsdaten, EOF sowie Lese- und Schreibfehler werden überwacht und lösen bei Bedarf einen kontrollierten Neuaufbau mit Backoff aus.
|
||||
|
||||
- **ON4KST-Protokollbefehle abgesichert:** Ausgehende Befehle werden zentral aufgebaut und auf gültige Kategorien, Locatoren und unerlaubte Frame-Trennzeichen geprüft. Da ON4KST pro TCP-Session nur einen Locator verwaltet, wird für beide Chat-Kategorien der Hauptlocator verwendet und eine abweichende zweite Konfiguration protokolliert, statt widersprüchliche Befehle an den Server zu senden.
|
||||
|
||||
- **QRG-Erkennung präzisiert:** Vollständige Frequenzangaben werden auch ohne Dezimaltrenner erkannt; die letzten drei Ziffern bilden dabei den kHz-Anteil. Relative Frequenzen bleiben unverändert, und nackte dreistellige Zahlen gelten weiterhin nur bei erkennbarem Frequenzkontext als QRG. Signalrapporte, Bandangaben und andere Zahlen erzeugen dadurch seltener falsche Frequenzen.
|
||||
|
||||
- **Stationsbezogener Frequenzkontext:** Bei relativen QRGs verwendet KST4Contest zuerst einen höchstens 30 Minuten alten Bandkontext derselben Station. Erst wenn dieser fehlt, wird das global konfigurierte Fallback-Band verwendet.
|
||||
|
||||
@@ -65,7 +74,7 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
|
||||
|
||||
- **Exakte Sked-Ziele:** Timeline und automatische Erinnerungen verwenden das vollständige sichtbare KST-Rufzeichen. Ein Sked für `DN9APW-2` wird nicht versehentlich an eine andere Variante desselben Basisrufzeichens gesendet.
|
||||
|
||||
- **Beacon und Autoantwort überarbeitet:** Beide Chat-Kategorien verwenden einen gemeinsamen Timer, behalten aber getrennte Aktivierungsschalter und Texte. Das zulässige Mindestintervall beträgt zwei Minuten; Nachrichtentexte sind auf 120 Zeichen begrenzt. Die gespeicherte Beacon-Aktivierung wird beim Start aus der Konfiguration übernommen.
|
||||
- **Beacon und Autoantwort überarbeitet:** Beide Chat-Kategorien verwenden einen gemeinsamen Timer, behalten aber getrennte Aktivierungsschalter und Texte. Das zulässige Mindestintervall beträgt eine Minute; Nachrichtentexte sind auf 120 Zeichen begrenzt. Die gespeicherte Beacon-Aktivierung wird beim Start aus der Konfiguration übernommen.
|
||||
|
||||
- **Variablen zentral aufgelöst:** Nachrichtenvariablen für Beacons, Shortcuts, Snippets und andere automatisch erzeugte Texte werden über einen gemeinsamen Resolver verarbeitet.
|
||||
|
||||
@@ -75,10 +84,28 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
|
||||
|
||||
- **DXLog-Gesamtlog übernommen:** Der UCXLog-kompatible UDP-Listener verarbeitet neben `contactinfo` auch `contactreplace`. Dadurch kann ein von DXLog.net als vollständiges Log ausgesendeter Datenbestand eingelesen werden.
|
||||
|
||||
- **Logger-Bandwerte vereinheitlicht:** Numerische sowie Meter- und Zentimeterangaben aus UCXLog-kompatiblen QSO-Paketen und die Band-IDs von Win-Test werden einmal normalisiert und danach einheitlich für Worked-Markierungen und Worked-Großfelder verwendet. Dadurch setzen insbesondere `2320`, `5760` und `10368` zuverlässig ihre vorhandenen Bandmarkierungen. Bei einem fehlenden oder unbekannten Band bleibt es beim globalen Worked-Status.
|
||||
|
||||
- **Simplelogfile-Verhalten präzisiert:** Die ausgewählte Textdatei wird einmal pro Minute mit einem festen Rufzeichenmuster ausgewertet. Treffer setzen den globalen Worked-Status aller aktiven Varianten des Basisrufzeichens, werden aber nicht in SQLite persistiert. Eine fehlende Datei wird angelegt; Lese- und Erstellungsfehler beenden die periodische Auswertung nicht. Ein Datenbank-Reset verändert die Datei nicht, sodass enthaltene Rufzeichen bei der nächsten Auswertung erneut als gearbeitet markiert werden.
|
||||
|
||||
- **Automatische QRG-Übernahme abgesichert:** `MYQRG` wird nur von einer aktivierten Schnittstelle aktualisiert, die tatsächlich gültige `RadioInfo`- beziehungsweise Win-Test-`STATUS`-Pakete liefert. Eine aktivierte, aber nicht liefernde Quelle ersetzt die notwendige Funktionsprüfung oder manuelle QRG-Pflege nicht.
|
||||
|
||||
- **Versionserkennung verbessert:** Versionsnummern werden semantisch verglichen, damit beispielsweise Patch-Versionen und Nightly-Stände nicht mehr durch eine einfache Fließkommazahl falsch eingeordnet werden.
|
||||
|
||||
### Behoben
|
||||
|
||||
- **Zuverlässige Benutzerliste beim Login:** Ungültige oder unvollständige `UA0`-Teilnehmerdatensätze werden einzeln verworfen und protokolliert, ohne die Verarbeitung der alphabetisch folgenden Teilnehmer abzubrechen. Die gültigen Einträge werden zunächst pro Kategorie gesammelt und erst mit dem ersten zugehörigen `UE`-Abschlussframe vollständig veröffentlicht.
|
||||
|
||||
- **Persistierter Worked-Status beim Listenaufbau:** Beim Abschluss jeder initialen ON4KST-Benutzerliste wird der SQLite-Zustand einmal geladen und vor der Veröffentlichung auf die neuen Chatmember angewendet. Das gilt für beide Kategorien, erneute Verbindungen und alle aktiven Varianten eines Basisrufzeichens.
|
||||
|
||||
- **Benutzerliste verschwindet nach dem Login:** ON4KST kann nach Namens-, Status- oder anderen Live-Änderungen weitere `UE`-Frames für dieselbe Kategorie senden. Wiederholte Abschlussframes werden jetzt erkannt und ignoriert, damit eine bereits gefüllte Benutzerliste nicht durch eine leere Momentaufnahme ersetzt wird.
|
||||
|
||||
- **Fehlgeschlagener Erstaufbau und Verbindungsverlust:** Wenn beim Programmstart keine Verbindung zum Server hergestellt werden kann, läuft KST4Contest nicht mehr in eine Endlos- oder Busy-Wait-Schleife. Die Oberfläche bleibt bedienbar und weitere Versuche erfolgen mit begrenztem Backoff. Auch ein vom Server geschlossener oder über längere Zeit stummer Socket wird zuverlässig erkannt.
|
||||
|
||||
- **Messagebus-Protokollierung:** Bereits korrekt verarbeitete ON4KST-Frames werden nicht mehr zusätzlich als `Critical, detected unhandled Chatmessage` gemeldet. Nur tatsächlich unbekannte Telegramme erreichen noch diesen Logzweig.
|
||||
|
||||
- **Passwort im Fehlerlog:** Das ON4KST-Passwort wird beim Verbindungsaufbau nicht mehr im Klartext in die Konsole oder Logdatei geschrieben.
|
||||
|
||||
- **Langzeitfehler der Stationsauswahl:** Die vom Message-Thread verwalteten Chatmember wurden von der JavaFX-Ansicht entkoppelt. Gleichzeitige Änderungen der Daten und Tabellenansicht führen dadurch nicht mehr nach längerer Laufzeit zu fehlerhaften Auswahlmodellen oder Concurrent-Modification-Problemen.
|
||||
|
||||
- **Keine Phantom-Chatmember durch UM3:** Historische oder zusätzliche Servermeldungen erzeugen keine Benutzerlisteneinträge für Stationen, die nicht tatsächlich im Chat angemeldet sind.
|
||||
@@ -107,6 +134,10 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
|
||||
|
||||
- Nightly-Pakete werden automatisiert aus dem aktuellen `main`-Branch gebaut. Stable- und Beta-Releases verwenden reproduzierbare Paketnamen für die unterstützten Plattformen.
|
||||
|
||||
- **Signierte und notarisierte macOS-Pakete:** Die DMG-Dateien für Apple Silicon und Intel sind mit einer Apple Developer ID signiert und von Apple notarisiert; das Notarisierungsticket ist in der DMG hinterlegt. Der erste Start funktioniert damit per Doppelklick, ohne den bisherigen Umweg über **Öffnen** im Kontextmenü, und die Prüfung gelingt auch ohne Internetverbindung. Betroffen sind Nightly-, Beta- und Stable-Pakete gleichermaßen. Die Windows-Pakete sind weiterhin nicht signiert.
|
||||
|
||||
- **Korrekte Bundle-Kennung und Version unter macOS:** Die Anwendung meldet sich jetzt als `de.x08.KST4Contest` statt als `kst4contest.view` und trägt die tatsächliche Versionsnummer im Bundle. Bisher wies jedes Release im Finder unter **Informationen** die Version `1.0` aus. Bestehende Einstellungen sind davon nicht betroffen, da KST4Contest seine Daten in `~/.praktiKST/` ablegt und nicht an der Bundle-Kennung festmacht.
|
||||
|
||||
### Bekannte Grenzen
|
||||
|
||||
- Die aktive Geländedatenquelle verwendet Open-Meteo mit Copernicus-GLO-90-Daten und höchstens 100 Höhenpunkten pro Strecke.
|
||||
@@ -119,6 +150,8 @@ v1.42 führt mehrere bisher getrennte Auswertungen zusammen. Bandinformationen,
|
||||
|
||||
- Eine genauere Konfiguration von Stationshöhe, Frequenz und K-Faktor wird in [Issue #74](https://github.com/praktimarc/kst4contest/issues/74) weiterverfolgt.
|
||||
|
||||
Die veröffentlichte Version ist als [Release v1.42.0](https://github.com/praktimarc/kst4contest/releases/tag/v1.42.0) verfügbar.
|
||||
|
||||
---
|
||||
|
||||
## v1.41.1 (2026-07-08)
|
||||
@@ -390,7 +423,6 @@ Erste öffentlich veröffentlichte Version. Grundfunktionen:
|
||||
|
||||
## Geplante Features
|
||||
|
||||
- `MYQTF`-Variable (eigene Antennenrichtung als Text)
|
||||
- ~~Lebensdauer für den Worked-Status (automatisches Zurücksetzen)~~ ✅ **Umgesetzt in v1.40** (3-Tage-Lebensdauer, kein manuelles Zurücksetzen mehr nötig)
|
||||
- Filterung des „Cluster & QSO der anderen"-Fensters auf eigenes QTF
|
||||
- Weitere Topografie-basierte Berechnungen für die Richtungswarnung
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
# Contest-Workflow mit KST4Contest
|
||||
|
||||
> [English version](en-Contest-Workflow) | Du liest gerade die deutsche Version
|
||||
|
||||
KST4Contest fasst Chat, Stationsauswahl, bekannte QRGs, Worked-Status, Skeds, Aircraft-Scatter-Zeiten und weitere Stationsdaten in einer gemeinsamen Oberfläche zusammen. Der Nutzen entsteht nicht aus einer einzelnen Anzeige, sondern aus dem Zusammenspiel dieser Informationen während des laufenden Contests.
|
||||
|
||||
Diese Seite beschreibt einen vollständigen Arbeitsablauf. Die einzelnen Funktionen und ihre technischen Grenzen werden weiterhin in den Kapiteln [Funktionen](de-Funktionen), [Benutzeroberfläche](de-Benutzeroberflaeche), [Log-Synchronisation](de-Log-Synchronisation) und [AirScout-Integration](de-AirScout-Integration) erläutert.
|
||||
|
||||
---
|
||||
|
||||
## Zweck und Grenzen
|
||||
|
||||
KST4Contest soll die Zeit zwischen einer erkannten Möglichkeit und dem tatsächlichen QSO verkürzen.
|
||||
|
||||
Das Programm kann unter anderem anzeigen:
|
||||
|
||||
- welche Stationen aktiv sind,
|
||||
- auf welchen Bändern und QRGs sie zuletzt erkannt wurden,
|
||||
- welche Stationen bereits gearbeitet wurden,
|
||||
- welche zusätzlichen Bänder noch infrage kommen,
|
||||
- welche Kandidaten zur aktuellen Antennenrichtung passen,
|
||||
- wann ein Aircraft-Scatter-Fenster erwartet wird und
|
||||
- welche Station gerade in eine für die eigene Station brauchbare Richtung arbeitet.
|
||||
|
||||
Diese Angaben bleiben Entscheidungshilfen. Ein hoher Prioritätsscore ist keine QSO-Wahrscheinlichkeit. Auch eine von AirScout mit 100 % bewertete Reflexionsgeometrie garantiert kein QSO. Ob eine QRG tatsächlich frei ist, die Gegenstation zuhört und der Funkweg unter den aktuellen Bedingungen funktioniert, muss der Operator weiterhin selbst beurteilen.
|
||||
|
||||
---
|
||||
|
||||
## Vor dem Contest
|
||||
|
||||
Die wesentlichen Einstellungen sollten nicht erst unmittelbar vor dem ersten interessanten Sked geprüft werden.
|
||||
|
||||
### Grundkonfiguration
|
||||
|
||||
Prüfe mindestens:
|
||||
|
||||
- eigenes Rufzeichen, Passwort und Locator,
|
||||
- primäre Chat-Kategorie,
|
||||
- Login und Einstellungen der zweiten Kategorie, falls sie verwendet wird,
|
||||
- lokal aktive Bänder,
|
||||
- Antennenöffnungswinkel,
|
||||
- maximale sinnvolle Entfernung,
|
||||
- `MYQRG` und gegebenenfalls `SECONDQRG`,
|
||||
- Log-Synchronisation und
|
||||
- benötigte Shortcuts, Snippets und Nachrichtenvariablen.
|
||||
|
||||
Antennenöffnungswinkel und maximale Entfernung sind stationsabhängig. Bei DM5M wird die reale Antennenanlage beispielsweise mit einem Öffnungswinkel von 69° und einer maximalen Entfernung von 900 km abgebildet. Das sind keine allgemeinen Vorgabewerte.
|
||||
|
||||
Speichere dauerhafte Änderungen mit **Save Settings**. Nach dem Verbindungsaufbau sollte der `LINK`-Indikator grün sein. Erst dann sind Anmeldung und Benutzerlistensynchronisation vollständig abgeschlossen.
|
||||
|
||||
### Automatische Antworten
|
||||
|
||||
Die automatische QRG-Antwort gehört zum aktiven Contest-Workflow. Sie beantwortet wiederkehrende QRG-Anfragen und nimmt dem Chatter damit einen Teil der Routinearbeit ab.
|
||||
|
||||
Davon zu unterscheiden ist die allgemeine automatische Antwort. Sie kann auf sämtliche eingehenden Anfragen reagieren und ist vor allem dann sinnvoll, wenn die Station vorübergehend nicht QRV ist oder nicht am Sked-Betrieb teilnehmen möchte. Sie erspart sowohl der eigenen Station als auch den anfragenden Stationen unnötige Folgefragen.
|
||||
|
||||
### Optionale Anbindungen
|
||||
|
||||
Aktiviere nur die Schnittstellen, die tatsächlich verwendet und vorher getestet wurden:
|
||||
|
||||
- Logprogramm beziehungsweise Simplelogfile,
|
||||
- TRX-Synchronisation,
|
||||
- AirScout,
|
||||
- PSTRotator,
|
||||
- Win-Test-Skedübergabe und
|
||||
- lokaler DX-Cluster-Server.
|
||||
|
||||
Ein Contest ist ein ungünstiger Zeitpunkt, um gleichzeitig die Funkbedingungen und eine erstmals aktivierte Netzwerkschnittstelle zu untersuchen.
|
||||
|
||||
Beim Simplelogfile sollte vor jedem Contest geprüft werden, ob das Logprogramm die aktuelle Contestdatei an den in KST4Contest ausgewählten Pfad schreibt. Ein Test-QSO muss innerhalb einer Minute als global gearbeitet erscheinen. KST4Contest setzt die aus dieser Datei abgeleiteten Worked-Markierungen beim Contestwechsel nicht automatisch zurück.
|
||||
|
||||
---
|
||||
|
||||
## Grundablauf während des Contests
|
||||
|
||||
Der typische Ablauf wiederholt sich:
|
||||
|
||||
1. CQ rufen oder einen vereinbarten Sked durchführen.
|
||||
2. Chat, Prioritätsliste, Karte und AP-Timeline beobachten.
|
||||
3. Einen geeigneten Kandidaten auswählen.
|
||||
4. Eigene oder fremde QRG festlegen.
|
||||
5. QSO versuchen.
|
||||
6. Erfolgreiches QSO sofort loggen.
|
||||
7. Eine weitere Bandmöglichkeit prüfen.
|
||||
8. Einen erfolglosen, aussagekräftigen Versuch mit **Sked fail** kennzeichnen.
|
||||
9. Zum CQ-Betrieb oder zum nächsten Kandidaten zurückkehren.
|
||||
|
||||
KST4Contest hält die benötigten Informationen zwischen diesen Schritten zusammen. Das eigentliche Umschalten, Rufen, Hören und Entscheiden bleibt bewusst beim Operator.
|
||||
|
||||
---
|
||||
|
||||
## CQ-Betrieb
|
||||
|
||||
Bei einer weitgehend festen CQ-QRG sollten `MYQRG` beziehungsweise `SECONDQRG` den tatsächlich verwendeten Frequenzen entsprechen. Eine aktivierte und tatsächlich liefernde TRX-Synchronisation kann `MYQRG` automatisch aktualisieren. Die Aktivierung allein reicht nicht: Vor dem Contest sollte mit einer realen Frequenzänderung geprüft werden, ob gültige Pakete ankommen. Ohne funktionierende automatische Quelle muss der Wert von Hand gepflegt werden.
|
||||
|
||||
Der Beacon kann die aktuelle QRG, den Locator und die Antennenrichtung regelmäßig im Chat veröffentlichen. Seine Variablen werden bei jedem Sendedurchlauf erneut ausgewertet.
|
||||
|
||||
Wird während des CQ-Betriebs über mehrere Frequenzen gescannt, sollte der Beacon deaktiviert werden. Eine automatisch veröffentlichte QRG ist nur hilfreich, solange sie noch stimmt.
|
||||
|
||||
Shortcuts und Snippets sollten die regelmäßig benötigten Nachrichten abdecken, beispielsweise:
|
||||
|
||||
- Bitte auf der eigenen QRG hören,
|
||||
- QRG der Gegenstation erfragen,
|
||||
- Wechsel zur QRG der Gegenstation ankündigen,
|
||||
- Antennenrichtung bestätigen und
|
||||
- einen Sked vorschlagen.
|
||||
|
||||
Einzelheiten stehen unter [Makros und Variablen](de-Makros-und-Variablen).
|
||||
|
||||
---
|
||||
|
||||
## Kandidaten auswählen
|
||||
|
||||
Die Benutzerliste kann mit QTF-, QRB-, Worked-, Band-, Aktivitäts-, New-Bands-, Tropo- und AirScout-Filtern auf den aktuellen Betriebszustand begrenzt werden.
|
||||
|
||||
Die Prioritätsliste und die AP-Timeline ergänzen diese Auswahl:
|
||||
|
||||
- Der Prioritätsscore fasst mehrere bekannte Kriterien zusammen.
|
||||
- Die AP-Timeline ordnet Skeds und erwartete Aircraft-Scatter-Möglichkeiten zeitlich ein.
|
||||
- Die Stationskarte zeigt die geografische Lage, Antennenrichtung und den Funkweg.
|
||||
- Der Worked- und Bandstatus verhindert unnötige Doppelarbeit.
|
||||
|
||||

|
||||
|
||||
Der Score ist eine Sortierhilfe. Prüfe vor einem Versuch weiterhin Rufzeichen, Kategorie, Band, QRG, Richtung, Entfernung und die Aktualität der zugrunde liegenden Informationen.
|
||||
|
||||
Eine bewusste Auswahl in Benutzerliste, Prioritätsliste, Timeline oder Karte übernimmt den konkreten Chatmember. Dabei bleiben das vollständige sichtbare Rufzeichen und die zugehörige Chat-Kategorie erhalten. KST4Contest bereitet anschließend `/cq RUFZEICHEN` im Sendfeld vor.
|
||||
|
||||
---
|
||||
|
||||
## Eigene oder fremde QRG verwenden
|
||||
|
||||
Wenn eine gute Ausbreitungsrichtung erkannt wird und ein passendes Flugzeug für einen Kandidaten vorhanden ist, wird die Gegenstation zunächst häufig auf die eigene QRG gebeten. Das ist besonders sinnvoll, wenn dort bereits CQ gerufen wird und die Station ohne weiteren Umbau sofort empfangen kann.
|
||||
|
||||
Reagiert die Gegenstation nicht, ist ihre eigene QRG geeigneter oder kann sie die angefragte QRG nicht verwenden, wird gewechselt. KST4Contest hält die zuletzt erkannten QRGs bereit, damit der Operator nicht erneut den gesamten Chatverlauf durchsuchen muss.
|
||||
|
||||
Auch ein gezielter Versuch auf der QRG eines Sked-Partners ist Teil des normalen Workflows. Entscheidend ist nicht, grundsätzlich auf der eigenen QRG zu bleiben, sondern die vorhandene Möglichkeit mit möglichst wenig Verzögerung zu nutzen.
|
||||
|
||||
Vor dem Wechsel sollten mindestens geprüft werden:
|
||||
|
||||
- korrektes Band,
|
||||
- vollständiges Zielrufzeichen,
|
||||
- QRG der Gegenstation,
|
||||
- Antennenrichtung,
|
||||
- erwartetes Aircraft-Scatter-Fenster und
|
||||
- Belegung der QRG.
|
||||
|
||||
---
|
||||
|
||||
## Richtungsgelegenheiten nutzen
|
||||
|
||||
Eine gerichtete Nachricht zwischen zwei anderen Stationen kann zeigen, dass der Absender seine Antenne ungefähr in Richtung des Empfängers ausgerichtet hat. Passt diese Richtung auch zur eigenen Station, markiert KST4Contest den Absender vorübergehend grün und fett.
|
||||
|
||||

|
||||
|
||||
Die Markierung erscheint in der Benutzerliste und den zugehörigen Ansichten. Bei geeigneter Cluster-Konfiguration und bekannter QRG kann die Gelegenheit zusätzlich über den lokalen DX-Cluster ausgegeben werden.
|
||||
|
||||
Damit stehen im entscheidenden Moment bereits mehrere Informationen zur Verfügung:
|
||||
|
||||
- vollständiges Rufzeichen,
|
||||
- Locator und Richtung,
|
||||
- zuletzt erkannte QRG,
|
||||
- Bandinformationen,
|
||||
- AirScout-Daten und
|
||||
- die aktuelle Reachability- beziehungsweise Tropo-Auswertung.
|
||||
|
||||
Der Operator muss diese Daten nicht erst zusammensuchen. Er muss lediglich entscheiden, ob die Gelegenheit den laufenden CQ-Betrieb kurz unterbrechen darf.
|
||||
|
||||
Bei DM5M lag die Erfolgsquote solcher opportunistischen Versuche nach der bisherigen praktischen Auswertung ungefähr bei 35–40 %. Dieser Wert beschreibt die Erfahrung einer konkreten Station. Er ist keine allgemeine Erfolgsprognose und hängt unter anderem von Band, Entfernung, Stationsausrüstung, Reaktionszeit und Ausbreitungsbedingungen ab.
|
||||
|
||||
Nach dem Versuch kann unmittelbar weiter CQ gerufen oder mit dem nächsten Sked fortgefahren werden.
|
||||
|
||||
---
|
||||
|
||||
## Skeds planen und auswerten
|
||||
|
||||
Ein Sked sollte mit dem tatsächlich vorgesehenen Band und einer realistischen Uhrzeit eingetragen werden. KST4Contest übernimmt ihn in die eigene Sked-Verwaltung und berücksichtigt ihn bei Erinnerungen, Timeline und Prioritätsberechnung.
|
||||
|
||||
Die Skeds werden nur für die laufende Programmsitzung verwaltet. Sie sind kein dauerhafter Ersatz für Contestlog oder Notizen.
|
||||
|
||||
Ist die Win-Test-Anbindung aktiviert, versucht KST4Contest den Sked zusätzlich an Win-Test zu übergeben. Fehlt eine verwendbare QRG oder passt das Band nicht, bleibt der interne Sked trotzdem erhalten. Lediglich die zusätzliche Übergabe kann dann entfallen.
|
||||
|
||||
### Fehlgeschlagene 100-%-Airplane-Skeds
|
||||
|
||||
Scheitert ein sorgfältig vorbereiteter Versuch trotz einer von AirScout mit 100 % bewerteten Reflexionsgeometrie, sollte die Station mit **Sked fail** gekennzeichnet werden.
|
||||
|
||||
Die 100-%-Anzeige ist keine Erfolgswahrscheinlichkeit. Ein Fehlschlag unter diesen Bedingungen ist aber ein brauchbarer betrieblicher Hinweis darauf, dass der Funkweg mit der aktuellen Stationskonfiguration und den aktuellen Bedingungen nicht funktioniert hat.
|
||||
|
||||
Die Kennzeichnung reduziert die Priorität der Station für den Rest der laufenden Sitzung. Dadurch können zunächst Kandidaten bearbeitet werden, für die noch keine vergleichbar deutliche negative Betriebserfahrung vorliegt.
|
||||
|
||||
**Sked fail** darf nicht als dauerhafte Aussage verstanden werden, dass die Station grundsätzlich nicht erreichbar ist. Andere Bedingungen, ein anderes Band oder eine geänderte Stationskonfiguration können zu einem anderen Ergebnis führen. Die Kennzeichnung kann zurückgesetzt werden und bleibt nicht über einen Programmneustart erhalten.
|
||||
|
||||
---
|
||||
|
||||
## Nach jedem QSO: Log und weiteres Band
|
||||
|
||||
Ein erfolgreiches QSO sollte sofort im angebundenen Logprogramm eingetragen werden. Nur dann können Worked-Status, Bandstatus, Filter und Prioritätsbewertung zeitnah aktualisiert werden.
|
||||
|
||||
Welche Details übernommen werden können, hängt von der Logquelle ab. Einige Schnittstellen liefern Band, QRG und Locator, während einfachere Quellen nur einen globalen Worked-Status melden.
|
||||
|
||||
Unmittelbar nach jedem Logeintrag sollte geprüft werden, ob für dieselbe Station ein weiteres gemeinsames, lokal aktiviertes und noch nicht gearbeitetes Band vorhanden ist. KST4Contest weist darauf mit `BAND+` und den Bandinformationen der Station hin, soweit die vorhandenen Daten eine solche Bewertung erlauben.
|
||||
|
||||
Diese Prüfung ist in allen Multiband-Betriebsarten sinnvoll. Die Gegenstation kann direkt mit einer konkreten Band- und Frequenzangabe weiterkoordiniert werden, bevor sie ihre Antenne wieder wegdreht oder einen anderen Sked beginnt.
|
||||
|
||||
Im Klartext: Das nächste mögliche QSO sollte geprüft werden, solange die Gegenstation noch erreichbar und der gemeinsame Kontext noch vorhanden ist.
|
||||
|
||||
Ist die Station auf einem angezeigten Band tatsächlich nicht QRV, sollte das Band als NOT QRV markiert werden. Dadurch verschwindet die unbrauchbare Möglichkeit aus Filtern und Bewertung, statt bei jeder Aktualisierung erneut aufzutauchen.
|
||||
|
||||
---
|
||||
|
||||
## Mehrkategorien- und Multibandbetrieb
|
||||
|
||||
Der Mehrkategorienbetrieb ist bei Multibandstationen keine Nebenfunktion. Sein wesentlicher Vorteil besteht darin, dass Informationen aus zwei Chat-Kategorien in einem gemeinsamen Arbeitsablauf ausgewertet werden.
|
||||
|
||||
Besonders groß ist der Vorteil bei:
|
||||
|
||||
- Einmann-Multibandstationen,
|
||||
- Multi-Operator-Multibandstationen mit einem zentralen Chat-Koordinator und
|
||||
- Stationen, die nach einem QSO regelmäßig direkt ein weiteres Band versuchen.
|
||||
|
||||
Worked-Status, bekannte Bandaktivitäten und Band Opportunities können gemeinsam bewertet werden. Das konkrete Nachrichtenziel behält trotzdem sein vollständiges Rufzeichen und seine Chat-Kategorie.
|
||||
|
||||
Dadurch kann der Chatter eine Station unmittelbar vom ersten QSO auf das nächste Band koordinieren, ohne Rufzeichen, QRG und Bandstatus erneut zusammensuchen zu müssen. Im praktischen Betrieb kann daraus eine sehr schnelle Folge nutzbarer QSO-Möglichkeiten entstehen. Genau an dieser Stelle entfaltet der Mehrkategorienbetrieb seinen größten Workflow-Vorteil.
|
||||
|
||||
Auch im Einmannbetrieb bleibt diese Arbeitsweise wirksam. Der Operator muss den Bandwechsel zwar selbst durchführen, erhält aber die nächste sinnvolle Möglichkeit bereits vorbereitet.
|
||||
|
||||
Multi-Multi-Stationen mit mehreren gleichzeitig arbeitenden Chattern profitieren ebenfalls. Dort müssen Zuständigkeiten, Bandwechsel und bereits laufende Anfragen allerdings klar koordiniert werden. Mehrere Chatter mit denselben Informationen sind hilfreich; mehrere widersprüchliche Sked-Anfragen an dieselbe Station eher nicht.
|
||||
|
||||
---
|
||||
|
||||
## Praxisbeispiele
|
||||
|
||||
### DM5M: Erst CQ, später mehr Skeds
|
||||
|
||||
Bei DM5M wird während der ersten vier bis fünf Stunden eines VHF-/UHF-Contests überwiegend CQ gerufen. Der Chat wird beobachtet, aber nur für wenige gezielte Eingriffe verwendet.
|
||||
|
||||
Später nimmt der Sked-Betrieb deutlich zu. Gute Ausbreitungsrichtungen, passende Aircraft-Scatter-Fenster, noch nicht gearbeitete Stationen und zusätzliche Bandmöglichkeiten werden dann gezielt miteinander kombiniert.
|
||||
|
||||
Eine geeignete Station wird zunächst auf die eigene QRG gebeten. Reagiert sie nicht oder ist die QRG bei der Gegenstation nicht verwendbar, wechselt DM5M auf deren QRG. Auch geplante Versuche direkt auf der QRG eines Sked-Partners gehören dazu.
|
||||
|
||||
Eine grün und fett markierte Richtungsgelegenheit kann den CQ-Betrieb kurzfristig unterbrechen. Nach dem Versuch wird unmittelbar weitergerufen oder der nächste Sked bearbeitet.
|
||||
|
||||
Dieser Ablauf ist ein Praxisbeispiel und keine verpflichtende Betriebsart. Andere Stationen können wesentlich früher skedden, dauerhaft zwischen QRGs wechseln oder den Chat von Beginn an intensiver nutzen.
|
||||
|
||||
### G1YBB: Richtungsgelegenheiten systematisch abarbeiten
|
||||
|
||||
G1YBB verwendet die Richtungsanzeige besonders konsequent. Grün markierte Stationen werden systematisch geprüft und nach Möglichkeit gearbeitet, während parallel der normale CQ-Betrieb weiterläuft.
|
||||
|
||||
KST4Contest automatisiert dabei nicht das QSO. Der Vorteil besteht darin, dass QRG, Richtung, Aircraft-Scatter-Informationen und weitere Bewertungsdaten bereits vorliegen, wenn die Gelegenheit entsteht. Die verbleibende Aufgabe ist eine schnelle betriebliche Entscheidung.
|
||||
|
||||
Eine weitere, besonders konsequente Betriebsweise nutzt die Stationskarte als geografische Arbeitsliste:
|
||||
|
||||
1. Der **wkd**-Filter blendet bereits gearbeitete Rufzeichen aus der Benutzerliste und damit auch aus der Karte aus.
|
||||
2. G1YBB wählt eine interessante Station auf der Karte aus.
|
||||
3. **Trigger cluster spot** übergibt die bekannte QRG an Minos.
|
||||
4. Ein Klick auf den Spot wechselt in Minos auf diese QRG.
|
||||
5. Nach dem QSO wird der Kontakt geloggt.
|
||||
6. Die Log-Synchronisation aktualisiert den Worked-Status; der Filter entfernt die Station anschließend aus Benutzerliste und Karte.
|
||||
|
||||
Die Karte wird so zu einer räumlichen Liste der noch abzuarbeitenden Stationen. Dieser Ablauf ist optional. Er setzt eine zuverlässig eingerichtete Log-Synchronisation und DX-Cluster-Verbindung voraus und ist vor allem für Operatoren interessant, die den Contest bewusst auf diese geografische Weise strukturieren möchten.
|
||||
|
||||
[G1YBB zeigt diesen Workflow im Video.](https://www.youtube.com/watch?v=lMQZMiSHlUI)
|
||||
|
||||
---
|
||||
|
||||
## Optionale Schnittstellen im Workflow
|
||||
|
||||
| Schnittstelle | Aufgabe im Contest |
|
||||
|---|---|
|
||||
| [Log-Synchronisation](de-Log-Synchronisation) | Aktualisiert Worked- und Bandstatus nach dem QSO |
|
||||
| [AirScout](de-AirScout-Integration) | Liefert Aircraft-Scatter-Kandidaten und erwartete Zeitfenster |
|
||||
| [PSTRotator](de-Konfiguration) | Übernimmt oder setzt die Antennenrichtung |
|
||||
| [Win-Test](de-Log-Synchronisation) | Kann angelegte Skeds zusätzlich an Win-Test übergeben |
|
||||
| [DX-Cluster-Server](de-DX-Cluster-Server) | Übergibt erkannte Möglichkeiten an verbundene Logprogramme |
|
||||
| [Stationskarte](de-Benutzeroberflaeche) | Zeigt Stationen, Richtungen, Auswahl und Funkweg |
|
||||
|
||||
Keine dieser Schnittstellen ist für den grundlegenden Chatbetrieb zwingend erforderlich. Ihr Wert entsteht dann, wenn sie zuverlässig eingerichtet ist und eine konkrete manuelle Aufgabe verkürzt.
|
||||
|
||||
---
|
||||
|
||||
## Was KST4Contest nicht entscheidet
|
||||
|
||||
KST4Contest entscheidet nicht:
|
||||
|
||||
- ob eine QRG tatsächlich frei ist,
|
||||
- ob die Gegenstation gerade hören kann,
|
||||
- ob ein Flugzeug ein QSO ermöglicht,
|
||||
- ob eine berechnete Funkstrecke unter den aktuellen Bedingungen funktioniert,
|
||||
- ob ein laufender CQ-Ruf für eine Gelegenheit unterbrochen werden sollte oder
|
||||
- welcher Kandidat für die aktuelle Conteststrategie den größten Wert besitzt.
|
||||
|
||||
Das Programm stellt die vorhandenen Informationen zusammen und hält sie aktuell. Die letzte Entscheidung bleibt beim Operator. Das ist keine Einschränkung des Workflows, sondern der Teil, für den weiterhin Funkbetrieb statt Tabellenkalkulation betrieben wird.
|
||||
@@ -23,7 +23,7 @@ Im Klartext: Die Information muss nicht erst im Chat gefunden, gelesen, gemerkt
|
||||
|
||||
---
|
||||
|
||||
## Wie wird eine Richtungsgelegenheit hergeleitet?
|
||||
## Automatische Spots aus Richtungsgelegenheiten
|
||||
|
||||
Angenommen, Station A schreibt eine gerichtete Nachricht an Station B. KST4Contest verwendet die Richtung von A zu B als Näherung für die aktuelle Antennenrichtung von Station A. Anschließend wird geprüft, ob die eigene Station aus Sicht von A innerhalb des angenommenen Antennenkorridors liegt.
|
||||
|
||||
@@ -51,6 +51,22 @@ Das Verfahren berücksichtigt weder Gelände noch aktuelle Ausbreitungsbedingung
|
||||
|
||||
---
|
||||
|
||||
## Manueller Spot für die ausgewählte Kartenstation
|
||||
|
||||
Ein Spot kann auch bewusst ausgelöst werden. Wähle dazu eine Station auf der Stationskarte und verwende **Trigger cluster spot** im Detailbereich.
|
||||
|
||||
Diese manuelle Auslösung benötigt keine zuvor erkannte gerichtete Nachricht. Auch maximaler QRB und Antennen-Öffnungswinkel entscheiden in diesem Fall nicht darüber, ob der Spot gesendet wird. Erforderlich sind:
|
||||
|
||||
- ein aktivierter lokaler DX-Cluster-Server,
|
||||
- mindestens ein verbundener DX-Cluster-Client und
|
||||
- eine für die ausgewählte Kartenstation verwendbare QRG.
|
||||
|
||||
Damit kann der Operator eine bereits ausgewählte Station gezielt in die Bandmap übernehmen, auch wenn die Bedingungen für einen automatischen Richtungs-Spot nicht vorliegen. Die Bedienung der Karte ist unter [Stationskarte](de-Benutzeroberflaeche#stationskarte) beschrieben.
|
||||
|
||||
Automatische und manuelle Spots werden ausschließlich an die mit KST4Contest verbundenen Clients gesendet. Es erfolgt keine Weiterleitung an einen öffentlichen Internet-Cluster.
|
||||
|
||||
---
|
||||
|
||||
## Welche Frequenz wird verwendet?
|
||||
|
||||
Ein DX-Cluster-Spot benötigt eine eindeutige Frequenz. KST4Contest verwendet dafür dieselbe QRG-Erkennung wie die Benutzerliste und die übrigen bandbezogenen Funktionen.
|
||||
@@ -143,15 +159,17 @@ Mehrere DX-Cluster-Clients können gleichzeitig verbunden werden. Ein erzeugter
|
||||
|
||||
## Verbindung testen
|
||||
|
||||
Die Schaltfläche **Send test spot** erzeugt einen neutralen Testeintrag:
|
||||
Die Schaltfläche **Send test spot** erzeugt folgenden Testeintrag:
|
||||
|
||||
```text
|
||||
Spotted callsign: DO5AMF
|
||||
Comment: KST4CONTEST TEST
|
||||
Comment: DXC test: You donated $100!
|
||||
Frequency: .300 des konfigurierten Fallback-Bandes
|
||||
```
|
||||
|
||||
Bei einem Fallback-Band von `144` erscheint der Spot daher auf ungefähr `144.300 MHz`.
|
||||
Bei einem Fallback-Band von `144 MHz` erscheint der Spot daher auf ungefähr `144.300 MHz`.
|
||||
|
||||
Der Kommentar ist ein bewusst beibehaltenes Easteregg. Er dient ausschließlich dazu, den Testspot im Logprogramm eindeutig wiederzuerkennen. Eine tatsächliche Spende oder sonstige externe Aktion wird dadurch selbstverständlich nicht ausgelöst.
|
||||
|
||||
Vor dem Test müssen drei Bedingungen erfüllt sein:
|
||||
|
||||
@@ -169,12 +187,21 @@ Ein Spot enthält:
|
||||
|
||||
- das konfigurierte Spotter-Rufzeichen,
|
||||
- die normalisierte Frequenz,
|
||||
- das Rufzeichen der erkannten Station,
|
||||
- das vollständige sichtbare Rufzeichen der erkannten oder ausgewählten Station,
|
||||
- den Locator,
|
||||
- Flugzeug-Scatter-Informationen, falls vorhanden,
|
||||
- die aktuelle UTC-Zeit.
|
||||
|
||||
Wenn für die Station aktuelle Aircraft-Scatter-Informationen vorliegen, kann KST4Contest diese als zusätzliche AP-Information in den Kommentar des Spots aufnehmen.
|
||||
Die Nutzzeile folgt einem festen, DXSpider-kompatiblen 75-Zeichen-Format. Das DX-Rufzeichen beginnt in Spalte 27, das Kommentarfeld umfasst genau 30 Zeichen und die UTC-Zeit beginnt in Spalte 71. Kurze Kommentare werden mit Leerzeichen aufgefüllt, längere kontrolliert auf 30 Zeichen begrenzt. Unterschiedlich lange Spotter-Rufzeichen und Frequenzen bis 24 GHz verschieben die nachfolgenden Felder nicht.
|
||||
|
||||
Das vollständige DX-Rufzeichen wird nicht abgeschnitten. Ist es länger als zwölf Zeichen, verwirft KST4Contest den Spot stattdessen kontrolliert und protokolliert den Grund.
|
||||
|
||||
Bei automatisch erzeugten Richtungs-Spots kann KST4Contest bis zu zwei aktuelle AirScout-Einträge als zusätzliche AP-Information in den Kommentar aufnehmen. Fehlende AirScout-Daten verhindern den Spot nicht. Ein manuell über die Stationskarte ausgelöster Spot verwendet den Locator der ausgewählten Station ohne diese optionale Ergänzung.
|
||||
|
||||
Ein kompakter Kommentar mit AirScout-Information sieht beispielsweise so aus:
|
||||
|
||||
```text
|
||||
JO51HK AP 1m/100%;4m/75%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -191,7 +218,7 @@ Prüfe:
|
||||
- Blockiert eine Firewall die Verbindung?
|
||||
- Ist im Logger das DX-Cluster-Fenster beziehungsweise die Bandmap aktiviert?
|
||||
|
||||
### Testspot funktioniert, aber reale Spots fehlen
|
||||
### Testspot funktioniert, aber automatische Spots fehlen
|
||||
|
||||
Dann funktioniert die Verbindung grundsätzlich. Für die betreffende Chat-Situation war wahrscheinlich mindestens eine fachliche Bedingung nicht erfüllt:
|
||||
|
||||
@@ -203,6 +230,10 @@ Dann funktioniert die Verbindung grundsätzlich. Für die betreffende Chat-Situa
|
||||
|
||||
KST4Contest sendet absichtlich nicht jede gefundene Frequenz an den Logger. Andernfalls würde aus einer Arbeitserleichterung sehr schnell eine lokale Spot-Schleuder.
|
||||
|
||||
### Manuell ausgelöster Spot fehlt
|
||||
|
||||
Prüfe, ob auf der Karte eine Station ausgewählt ist und für sie eine verwendbare QRG vorliegt. Der lokale Server muss aktiviert und mindestens ein Client verbunden sein. Die Geometrie einer gerichteten Nachricht, maximaler QRB und Antennen-Öffnungswinkel sind für die manuelle Auslösung keine Voraussetzungen.
|
||||
|
||||
### Der Spot erscheint auf dem falschen Band
|
||||
|
||||
Prüfe zuerst, welche Frequenzen für die betreffende Station innerhalb der letzten 30 Minuten erkannt wurden. Bei einer relativen Angabe hat dieser Stationskontext Vorrang vor dem globalen Fallback.
|
||||
@@ -221,5 +252,6 @@ Die Schnittstelle wurde mit folgenden Logprogrammen verwendet:
|
||||
|
||||
- UCXLog
|
||||
- N1MM+
|
||||
- Minos
|
||||
|
||||
Weitere Logger können funktionieren, wenn sie eine normale TCP-Verbindung zu einem DX-Cluster-Server unterstützen.
|
||||
|
||||
@@ -23,6 +23,10 @@ Angenommen, Station A schreibt eine gerichtete Nachricht an Station B:
|
||||
|
||||
Ein eingetragener Öffnungswinkel von `70°` ergibt damit einen angenommenen Korridor von jeweils `35°` links und rechts der Richtung von Station A zu Station B.
|
||||
|
||||

|
||||
|
||||
Im dargestellten Beispiel verläuft der grüne Korridor von F5FEN in Richtung DM5M. Sendet F5FEN eine gerichtete Nachricht an DM5M, prüft KST4Contest, ob die eigene Station innerhalb dieses Korridors liegt. Bei einer Antwort von DM5M an F5FEN wird die Richtung neu berechnet: Dann beginnt der blaue Korridor bei DM5M und zeigt in Gegenrichtung auf F5FEN. Bewertet wird also bei jeder Nachricht die angenommene Antennenrichtung des jeweiligen Absenders.
|
||||
|
||||
| Beispiel | Ergebnis |
|
||||
|---|---|
|
||||
| Richtung A → B: `120°`, Richtung A → eigene Station: `145°` | Winkeldifferenz `25°`: Richtungsgelegenheit erkannt |
|
||||
@@ -80,13 +84,15 @@ KST4Contest wertet deshalb den Text jeder öffentlichen und gerichteten Chat-Nac
|
||||
|
||||
| Schreibweise | Beispiel | Verarbeitung |
|
||||
|---|---|---|
|
||||
| Vollständige Frequenz | `144.210`, `432,088`, `10368.100` | Das Band ergibt sich direkt aus der Frequenz. |
|
||||
| Vollständige Frequenz | `144.210`, `432,088`, `144307`, `10368100` | Das Band ergibt sich direkt aus der Frequenz. |
|
||||
| Relative Frequenz mit Punkt oder Komma | `.210`, `,088` | Das Band wird aus dem Stationskontext oder dem konfigurierten Fallback ergänzt. |
|
||||
| Dreistellige Frequenz mit Textkontext | `qrg 210`, `freq is 210`, `on 210`, `210 MHz` | Die Zahl wird als relative Frequenz behandelt. |
|
||||
| Dreistellige Zahl ohne Frequenzkontext | `210`, `599`, `144` | Die Zahl wird absichtlich nicht als QRG übernommen. |
|
||||
|
||||
Die letzte Einschränkung verhindert plausible, aber falsche Ergebnisse. Mit einem Fallback von `144 MHz` ließe sich ein Signalrapport `599` technisch problemlos zu `144.599 MHz` zusammensetzen. Das Ergebnis wäre formal gültig und fachlich trotzdem Unsinn.
|
||||
|
||||
Eine vollständige Frequenz kann auch ohne Punkt oder Komma geschrieben sein. KST4Contest behandelt dabei die letzten drei Ziffern als kHz-Anteil: `144307` im Namensfeld wird zu `144.307 MHz`, `10368100` in einer öffentlichen oder gerichteten Chatnachricht zu `10368.100 MHz`. Der Wert wird nur übernommen, wenn die daraus entstehende Frequenz innerhalb eines unterstützten Bandbereichs liegt.
|
||||
|
||||
### Wie wird das Band einer relativen QRG bestimmt?
|
||||
|
||||
KST4Contest verwendet folgende Reihenfolge:
|
||||
@@ -132,7 +138,7 @@ Diese Trennung ist notwendig. Ein bereits gearbeitetes Rufzeichen kann auf einem
|
||||
|
||||
Die [Log-Synchronisation](de-Log-Synchronisation) übernimmt neue QSOs aus dem Logprogramm. Welche Informationen dabei zur Verfügung stehen, hängt von der verwendeten Schnittstelle ab:
|
||||
|
||||
- Der dateibasierte Simplelogfile-Interpreter erkennt nur das Rufzeichen. Er kann deshalb lediglich den globalen Worked-Status setzen.
|
||||
- Der dateibasierte Simplelogfile-Interpreter liest die ausgewählte Textdatei einmal pro Minute mit einem festen Rufzeichenmuster. Treffer werden auf das Basisrufzeichen normalisiert und setzen für alle aktiven Varianten lediglich den globalen Worked-Status.
|
||||
- Die QSO-UDP-Schnittstellen und der Win-Test-Netzwerk-Listener können zusätzlich das Band übernehmen.
|
||||
- Enthält das Logpaket einen gültigen Locator, speichert KST4Contest außerdem das gearbeitete vierstellige Großfeld für dieses Band.
|
||||
|
||||
@@ -201,24 +207,12 @@ Im Klartext: Ein erkannter Hinweis bedeutet „wahrscheinlich auf diesem Band ak
|
||||
|
||||
### Speicherung und Lebensdauer
|
||||
|
||||
Worked-, NOT-QRV- und Großfeldinformationen werden in der internen SQLite-Datenbank gespeichert und beim nächsten Start wieder geladen. Die Einträge laufen nach drei Tagen automatisch ab. Ein Reset vor jedem Contest ist deshalb normalerweise nicht erforderlich.
|
||||
Worked-, NOT-QRV- und Großfeldinformationen aus den Netzwerkschnittstellen sowie manuelle Markierungen werden in der internen SQLite-Datenbank gespeichert und beim nächsten Start wieder geladen. Die Einträge laufen nach drei Tagen automatisch ab. Ein Reset vor jedem Contest ist deshalb normalerweise nicht erforderlich.
|
||||
|
||||
Simplelogfile-Treffer sind davon ausgenommen. Sie werden nicht in SQLite persistiert, sondern bei jedem Programmlauf aus der ausgewählten Datei abgeleitet. Die Datei ist damit die dauerhafte Quelle. Der Interpreter entfernt während der laufenden Sitzung keine bereits gesetzte Worked-Markierung und setzt den Zustand beim Wechsel zu einem neuen Contest nicht automatisch zurück. Ein manueller Datenbank-Reset verändert oder leert die Datei ebenfalls nicht. Darin enthaltene Rufzeichen werden bei der nächsten Auswertung innerhalb einer Minute erneut als gearbeitet markiert.
|
||||
|
||||
Ein manueller Reset unter **Workedstn database** entfernt sämtliche Worked-Markierungen, NOT-QRV-Tags und gespeicherten Worked-Großfelder. Die bekannten Rufzeichenzeilen bleiben dabei in der Datenbank erhalten. Einzelheiten: [Worked Station Database Settings](de-Konfiguration#worked-station-database-settings-gearbeitete-stationen-datenbank).
|
||||
|
||||
|
||||
---
|
||||
|
||||
## NOT-QRV-Tags (ab v1.2)
|
||||
|
||||
Wenn eine Station mitteilt, dass sie auf einem bestimmten Band nicht QRV ist, kann dies manuell markiert werden:
|
||||
|
||||
1. Station in der Benutzerliste auswählen.
|
||||
2. Rechtsklick → NOT-QRV für das entsprechende Band setzen.
|
||||
|
||||
Diese Tags werden in der internen Datenbank gespeichert und bleiben nach einem Neustart von KST4Contest erhalten. Zurücksetzen über die Einstellungen möglich.
|
||||
|
||||
**Nutzen**: Verhindert wiederholte Sked-Anfragen auf Bändern, auf denen die Station nicht QRV ist – schont sowohl die eigenen Nerven als auch die der Gegenstation.
|
||||
|
||||
---
|
||||
|
||||
## Richtungsfilter (Direction Filter)
|
||||
@@ -247,19 +241,33 @@ Die Filter oberhalb der Benutzerliste greifen auf dieselben Informationen zurüc
|
||||
|
||||
Mehrere aktivierte Filter werden gemeinsam angewendet. Eine Station bleibt nur sichtbar, wenn sie alle gewählten Bedingungen erfüllt. Die Filter reagieren unmittelbar auf neue Logeinträge und geänderte NOT-QRV-Markierungen.
|
||||
|
||||
Bedienung und Aufbau der Filterleiste: [Benutzeroberfläche – Filter](de-Benutzeroberflaeche#filter).
|
||||
Bedienung und Aufbau der Filterleiste: [Benutzeroberfläche – Filter](de-Benutzeroberflaeche#filter-und-reachability-steuerung).
|
||||
|
||||
---
|
||||
|
||||
## Farbige PM-Zeilen (ab v1.25)
|
||||
|
||||
Neue Privatnachrichten erscheinen in **Rot**. Die Farbe wechselt alle 30 Sekunden über Gelb bis Weiß – wie ein Regenbogen-Fade. So ist auf einen Blick erkennbar, wie aktuell eine Nachricht ist.
|
||||
Neue eingehende Privatnachrichten werden in mehreren grünen Altersstufen hervorgehoben. Mit zunehmendem Alter wird das Grün schrittweise gedämpfter:
|
||||
|
||||
| Alter der Nachricht | Darstellung |
|
||||
|---|---|
|
||||
| bis einschließlich 30 Sekunden | erste grüne Stufe |
|
||||
| 31 bis 60 Sekunden | zweite grüne Stufe |
|
||||
| 61 bis 90 Sekunden | dritte grüne Stufe |
|
||||
| 91 bis 120 Sekunden | vierte grüne Stufe |
|
||||
| 121 bis 180 Sekunden | fünfte grüne Stufe |
|
||||
| 181 bis 300 Sekunden | sechste grüne Stufe |
|
||||
| ab 301 Sekunden | normale Tabellenfarbe |
|
||||
|
||||
Die Tabelle aktualisiert die Altersdarstellung alle fünf Sekunden. Ein Grenzübergang kann deshalb erst beim nächsten Aktualisierungslauf sichtbar werden. Nach fünf Minuten bleibt keine Altersklasse an der Zeile haften; auch wiederverwendete oder leere Tabellenzeilen kehren zu ihrem normalen Stil zurück.
|
||||
|
||||
Eigene Nachrichten erhalten weiterhin eine separate Hervorhebung und verwenden nicht die grüne Altersskala.
|
||||
|
||||
*(Idee von IU3OAR, Gianluca Costantino – danke!)*
|
||||
|
||||
---
|
||||
|
||||
## PM-Abfang (Catching Personal Messages)
|
||||
## PM-Abfang (Catching Personal Messages, ab v1.1)
|
||||
|
||||
Manche Nutzer senden Direktnachrichten versehentlich öffentlich, z. B.:
|
||||
|
||||
@@ -267,7 +275,17 @@ Manche Nutzer senden Direktnachrichten versehentlich öffentlich, z. B.:
|
||||
(DM5M) pse ur qrg
|
||||
```
|
||||
|
||||
KST4Contest erkennt solche Nachrichten, die das eigene Rufzeichen enthalten, und sortiert sie automatisch in die **Privatnachrichten-Tabelle** ein. So gehen keine Nachrichten verloren.
|
||||
KST4Contest sucht im Text aller nicht selbst gesendeten Nachrichten ohne Beachtung der Groß- und Kleinschreibung nach dem konfigurierten eigenen Login-Rufzeichen. Ein Treffer erscheint zusätzlich in der **Privatnachrichten-Tabelle**. Das gilt sowohl für öffentliche Nachrichten an `ALL` als auch für gerichtete Nachrichten zwischen anderen Chatteilnehmern.
|
||||
|
||||
Gerade bei einer fremden gerichteten Nachricht wirkt das etwas wie **„Lästererkennung“**. Das ist eine flapsige Nebenbezeichnung, kein formaler Funktionsname.
|
||||
|
||||
Die ursprüngliche Nachricht bleibt unverändert: Der tatsächliche Empfänger bleibt `ALL` beziehungsweise der andere Chatteilnehmer; auch Text, Chat-Kategorie und Routing bleiben erhalten. PM-Catching ergänzt nur eine weitere Ansicht derselben Nachricht.
|
||||
|
||||
Die Textsuche versteht keine Absicht. Ein Schreibfehler oder ein verkürztes Rufzeichen wird nicht erkannt. Umgekehrt kann eine bloße Erwähnung des vollständig geschriebenen Rufzeichens einen Treffer erzeugen, obwohl keine Antwort erwartet wird.
|
||||
|
||||
Wird eine eingehende Zeile in der PM-Tabelle ausgewählt, bereitet KST4Contest eine Antwort an ihren Absender vor. Bei einer eigenen ausgehenden Nachricht wird stattdessen der ursprüngliche Empfänger als Ziel wiederhergestellt. Die Auswahl versendet noch keine Nachricht.
|
||||
|
||||
Auch abgefangene Zeilen verwenden die normale Altersanzeige der PM-Tabelle. Sie lösen jedoch weder den einfachen PM-Hinweiston noch die CW- oder Sprachausgabe für ein eingehendes Rufzeichen aus. Dasselbe gilt für Nachrichten, die durch das QSO-Monitoring zusätzlich in der PM-Tabelle erscheinen.
|
||||
|
||||
---
|
||||
|
||||
@@ -277,9 +295,13 @@ Nicht jede im ON4KST-Chat eingeloggte Station nimmt am gerade laufenden Contest
|
||||
|
||||
KST4Contest kann darauf mit einem vorher festgelegten Text reagieren. Die eingehende Privatnachricht bleibt dabei sichtbar; sie wird weder blockiert noch verworfen. Davon getrennt lässt sich eine QRG-Antwort aktivieren, die auf typische Fragen wie `qrg?`, `freq?` oder `pse qrg` reagiert.
|
||||
|
||||
Bei zwei gleichzeitig geöffneten Chat-Kategorien bleibt der Zusammenhang erhalten: Die Antwort wird in der Kategorie der eingegangenen Nachricht gesendet. Eine QRG-Anfrage erhält außerdem nur die QRG dieser Kategorie und nicht eine Liste aller konfigurierten Frequenzen.
|
||||
Bei zwei gleichzeitig geöffneten Chat-Kategorien bleibt der Zusammenhang erhalten: Die Antwort wird an das vollständige Absenderrufzeichen und in der Kategorie der eingegangenen Nachricht gesendet. Eine QRG-Anfrage erhält außerdem nur die QRG dieser Kategorie und nicht eine Liste aller konfigurierten Frequenzen.
|
||||
|
||||
Automatische Antworten benötigen Grenzen. KST4Contest versieht sie daher mit `[KST4C Automsg]`, ignoriert entsprechend gekennzeichnete Nachrichten bei der allgemeinen und QRG-bezogenen Antwort und begrenzt weitere Antworten an dieselbe Station in derselben Kategorie auf eine Nachricht innerhalb von zwei Minuten. Der Schutz gilt gemeinsam für beide Antwortarten.
|
||||
Fehlt die QRG der betreffenden Kategorie, sendet KST4Contest keine inhaltslose Auskunft. Auch ein leerer oder protokollwidriger allgemeiner Antworttext wird nicht in die Sendequeue übernommen.
|
||||
|
||||
Automatische Antworten benötigen Grenzen. KST4Contest versieht sie daher mit `[KST4C Automsg]`, ignoriert entsprechend gekennzeichnete Nachrichten und begrenzt weitere Antworten an dasselbe vollständige Rufzeichen in derselben Kategorie auf eine Nachricht innerhalb von zwei Minuten. Der Schutz gilt gemeinsam für die allgemeine und die QRG-bezogene Antwort.
|
||||
|
||||
Die Sperrzeit wird nur nach einer vollständigen und lokal gültigen Antwort gestartet. Eine fehlende QRG oder ein verworfener Antworttext verhindert daher keine spätere gültige Antwort.
|
||||
|
||||
Im Klartext: Die Funktion verhindert keine Massenanfragen. Sie verhindert aber, dass der Empfänger jede davon einzeln mit derselben Absage beantworten muss. Sie soll keine Unterhaltung simulieren und erst recht keine endlose Diskussion mit einem zweiten automatischen Client beginnen.
|
||||
|
||||
@@ -287,23 +309,24 @@ Konfiguration, erkannte QRG-Anfragen und genaue Kategorienzuordnung: [Konfigurat
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Multi-Channel-Login (ab v1.26)
|
||||
|
||||
Gleichzeitiger Login in **zwei Chat-Kategorien** (z. B. 144 MHz und 432 MHz). Beide Chats werden parallel überwacht.
|
||||
KST4Contest meldet sich mit einem lokalen Login-Rufzeichen und Passwort einmal bei ON4KST an. Ein gemeinsamer Locator gilt für die gesamte TCP-Sitzung. Eine zweite Chat-Kategorie (z. B. 144 MHz und 432 MHz) wird per Single Sign-on innerhalb derselben Sitzung ergänzt. Beide Kategorien werden parallel überwacht; das sichtbare kategoriebasierte Namensfeld, der Nachrichtenkontext, die QRG und der Beacon bleiben je Kategorie getrennt.
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode (ab v1.26)
|
||||
|
||||
Aktivierbar über: **Window → Use Dark Mode**
|
||||
Aktivierbar über **Windows → Use dark mode design**. Mit **Windows → Use default mode design** wird wieder auf das normale helle Farbschema umgeschaltet.
|
||||
|
||||
Für individuelle Farbanpassungen: CSS-Datei bearbeiten (Pfad in den Programmunterlagen).
|
||||
Die grüne Altersskala der Privatnachrichten bleibt in beiden Darstellungen erhalten. Textfarbe, normale Tabellenfarbe und die separate Hervorhebung eigener Nachrichten folgen dem jeweils geladenen Standarddesign.
|
||||
|
||||
---
|
||||
|
||||
## Opposite Station Multi-Callsign Login-Tagging (ab v1.26)
|
||||
|
||||
Unterstützung für Stationen, die mit mehreren Rufzeichen gleichzeitig im Chat aktiv sind (z. B. Expedition-Setups).
|
||||
Unterstützung für Gegenstationen, die mit mehreren vollständigen sichtbaren Rufzeichenvarianten gleichzeitig im Chat aktiv sind (z. B. Expedition-Setups). Vollständiges Rufzeichen und Chat-Kategorie bleiben getrennte Teilnehmeridentitäten; Worked-, Band- und Prioritätsdaten werden über das Basisrufzeichen gemeinsam ausgewertet.
|
||||
|
||||
---
|
||||
|
||||
@@ -315,7 +338,7 @@ Für ausgewählte Stationen in der Benutzerliste gibt es direkte Buttons, um das
|
||||
|
||||
## Skeds und Sked-Erinnerungen
|
||||
|
||||
> Verfügbar ab v1.40; Band-, Rufzeichen- und Win-Test-Behandlung erweitert in Nightly / v1.42.
|
||||
> Verfügbar ab v1.40; Band-, Rufzeichen- und Win-Test-Behandlung erweitert in v1.42.
|
||||
|
||||
Ein Sked ist mehr als eine Erinnerung an eine Uhrzeit. Er muss während des laufenden Contestbetriebs rechtzeitig sichtbar werden, die vereinbarte Station priorisieren und – sofern gewünscht – die Gegenstation noch einmal an den Termin erinnern.
|
||||
|
||||
@@ -389,19 +412,56 @@ Die automatische Bandvorauswahl ist eine Herleitung aus vorhandenen Chatinformat
|
||||
|
||||
Bedienung: [Stationsinfo-Panel](de-Benutzeroberflaeche#stationsinfo-panel-further-info)
|
||||
|
||||
Darstellung: [AP- und Sked-Timeline](#ap-und-sked-timeline)
|
||||
Darstellung: [AP- und Sked-Timeline](#ap--und-sked-timeline)
|
||||
|
||||
Win-Test-Übergabe: [Log-Synchronisation – Win-Test](de-Log-Synchronisation#win-test)
|
||||
|
||||
|
||||
## QSO-Monitoring (ab v1.31)
|
||||
|
||||
Für ausgewählte Rufzeichen kann KST4Contest gerichtete Nachrichten zusätzlich in der PM-Tabelle anzeigen. Dabei werden sowohl Nachrichten berücksichtigt, die das überwachte Rufzeichen sendet, als auch Nachrichten, die an dieses Rufzeichen gerichtet sind.
|
||||
Während eines Contests kann es sinnvoll sein, die Kommunikation einer bestimmten Station zu verfolgen. Das betrifft nicht nur seltene Stationen oder DXpeditionen. Auch die Sked-Absprachen einer anderen Station des eigenen Teams können im öffentlichen Nachrichtenaufkommen schnell übersehen werden.
|
||||
|
||||
Die Nachricht bleibt gleichzeitig in ihrer ursprünglichen Tabelle erhalten und wird im PM-Fenster mit Absender und Empfänger als überwachte Kommunikation gekennzeichnet.
|
||||
KST4Contest kann deshalb alle Nachrichten, die eine überwachte Station sendet oder empfängt, zusätzlich in der PM-Tabelle anzeigen. Die Nachricht bleibt gleichzeitig in ihrer ursprünglichen Tabelle erhalten.
|
||||
|
||||
Die Überwachung arbeitet mit dem normalisierten Basisrufzeichen. Ein Eintrag für `DN9APW` erfasst daher auch Nachrichten von oder an:
|
||||
|
||||
```text
|
||||
DN9APW-2
|
||||
DN9APW-70
|
||||
DN9APW-144
|
||||
DN9APW-432
|
||||
```
|
||||
|
||||
Auch wenn `DN9APW-70` in die Monitoring-Liste eingegeben wird, speichert KST4Contest nur `DN9APW`. Eine Station mit mehreren band- oder arbeitsplatzbezogenen KST-Suffixen muss dadurch nur einmal eingetragen werden.
|
||||
|
||||
Die eigentliche Nachricht behält trotzdem die vollständigen Rufzeichen:
|
||||
|
||||
```text
|
||||
Sniffed: (DN9APW-70 > 9A0BB-23) pse sked 19:30
|
||||
```
|
||||
|
||||
Das ist ein wichtiger Unterschied. Das Monitoring fasst die Varianten zusammen, damit keine Nachricht übersehen wird. Das Nachrichtenrouting fasst sie nicht zusammen, weil andernfalls nicht mehr eindeutig wäre, welcher Login tatsächlich angesprochen wurde.
|
||||
|
||||
Das Basisrufzeichen wird außerdem unabhängig von der Chat-Kategorie ausgewertet. Sind in derselben ON4KST-Sitzung zwei Kategorien aktiv, gilt derselbe Monitoring-Eintrag für beide Nachrichtenströme. Die Kategorie der einzelnen Nachricht bleibt dabei erhalten.
|
||||
|
||||
Erfasst werden:
|
||||
|
||||
- gerichtete Nachrichten, die eine überwachte Station sendet,
|
||||
- gerichtete Nachrichten an eine überwachte Station und
|
||||
- öffentliche Nachrichten, die eine überwachte Station an `ALL` sendet.
|
||||
|
||||
Eine bloße Nennung des Rufzeichens im Nachrichtentext löst das Monitoring nicht aus.
|
||||
|
||||
Nachrichten, die bereits direkt an das eigene Rufzeichen gerichtet sind, erscheinen als normale Privatnachrichten. Sie werden nicht zusätzlich als `Sniffed:` gekennzeichnet. Das QSO-Monitoring erzeugt außerdem keinen eigenen Hinweiston. Die akustische Benachrichtigung für tatsächlich an die eigene Station gerichtete Privatnachrichten bleibt davon unabhängig.
|
||||
|
||||
Die Funktion fordert keine zusätzlichen Nachrichten vom ON4KST-Server an. Sie ordnet lediglich den bereits empfangenen Chatverkehr zusätzlich in die PM-Ansicht ein.
|
||||
|
||||
Im Klartext: Das QSO-Monitoring entscheidet nicht, ob eine Nachricht tatsächlich einen Sked enthält oder für den eigenen Contestbetrieb relevant ist. Es sorgt lediglich dafür, dass die Kommunikation der ausgewählten Station nicht zwischen den übrigen Nachrichten verschwindet.
|
||||
|
||||
Konfiguration: [Konfiguration – Sniffer-Einstellungen](de-Konfiguration#sniffer-einstellungen-ab-v131)
|
||||
|
||||
Trennung vollständiger Rufzeichen und Chat-Kategorien: [Multi-Channel-Login und Rufzeichenvarianten](#multi-channel-login-ab-v126)
|
||||
|
||||
---
|
||||
|
||||
## Win-Test-Integration
|
||||
@@ -422,14 +482,54 @@ Einstellungen: [Win-Test-Netzwerk-Listener](de-Konfiguration#win-test-netzwerk-l
|
||||
|
||||
|
||||
|
||||
## PSTRotator-Interface (ab v1.31, vollständig ab v1.40)
|
||||
## PSTRotator-Interface (ab v1.31, vollständig konfigurierbar ab v1.40)
|
||||
|
||||
KST4Contest kann die Antennenrichtung direkt über **PSTRotator** steuern. Wenn in der Benutzerliste eine Station ausgewählt wird, kann der Rotator automatisch auf den QTF zur ausgewählten Station gedreht werden.
|
||||
KST4Contest kann die Antenne über die UDP-Schnittstelle von PSTRotator auf die ausgewählte Gegenstation drehen. Der dafür verwendete Azimut wird aus dem eigenen und dem fremden Locator berechnet.
|
||||
|
||||
Konfiguration: [Konfiguration – PSTRotator-Einstellungen](de-Konfiguration#pstrotator-einstellungen-ab-v131)
|
||||
Nach der Auswahl einer Station steht im **Further Info**-Bereich der Button **Turn ant1 to …** zur Verfügung:
|
||||
|
||||

|
||||
|
||||
Ein Klick führt folgende Schritte aus:
|
||||
|
||||
1. KST4Contest beendet den Tracking-Modus von PSTRotator.
|
||||
2. Der QTF der ausgewählten Station wird als ganzzahliger Azimut übertragen.
|
||||
3. PSTRotator steuert den konfigurierten Rotator.
|
||||
4. Die zurückgemeldete Position wird als aktuelle QTF in KST4Contest übernommen.
|
||||
|
||||
Der Button bleibt auch sichtbar, wenn die PSTRotator-Integration deaktiviert ist. In diesem Fall wird kein Rotatorbefehl gesendet.
|
||||
|
||||
### Rückmeldung und SPID-Kompatibilität
|
||||
|
||||
KST4Contest fragt die aktuelle Azimutposition alle zwei Sekunden ab. Die Rückmeldung aktualisiert das eigene QTF-Feld und damit alle Funktionen, die von der Antennenrichtung abhängen.
|
||||
|
||||
Einige SPID-Konfigurationen übernehmen den ersten Richtungsbefehl gelegentlich nicht. KST4Contest prüft deshalb zwei Sekunden nach dem Befehl, ob PSTRotator eine Bewegung beziehungsweise das Erreichen des Ziels gemeldet hat.
|
||||
|
||||
Ist die Position unverändert und wurde das Ziel nicht erreicht, sendet KST4Contest einmalig eine Kompatibilitätssequenz über `0°` und anschließend erneut den eigentlichen Zielwert.
|
||||
|
||||
Diese Prüfung läuft im Hintergrund. Die Benutzeroberfläche bleibt während der zwei Sekunden bedienbar. Wird zwischenzeitlich ein neuer Drehbefehl ausgelöst, ersetzt er die noch ausstehende Prüfung des vorherigen Befehls.
|
||||
|
||||
### Was bestätigt die Positionsanzeige?
|
||||
|
||||
Die angezeigte QTF ist die von PSTRotator gemeldete Azimutposition. Sie bestätigt, dass KST4Contest eine auswertbare UDP-Rückmeldung erhalten hat.
|
||||
|
||||
Sie beweist nicht in jedem Aufbau, dass die Antenne mechanisch exakt in dieser Richtung steht. Das hängt von PSTRotator, dem angeschlossenen Controller, der Kalibrierung, möglichen Offsets und der tatsächlichen Rückmeldung des Rotors ab.
|
||||
|
||||
UDP selbst bestätigt außerdem keine Paketzustellung. Bleibt die QTF unverändert, sollten daher zuerst folgende Punkte geprüft werden:
|
||||
|
||||
- **UDP Control** ist in PSTRotator aktiviert.
|
||||
- Host und Steuerport stimmen überein.
|
||||
- Der Rückmeldeport `Steuerport + 1` ist frei.
|
||||
- Die Firewall lässt beide UDP-Richtungen zu.
|
||||
- PSTRotator zeigt selbst eine plausible Rotorposition an.
|
||||
|
||||
Im Klartext: KST4Contest liefert die Zielrichtung und verarbeitet die gemeldete Position. Die mechanische Realität bleibt Aufgabe des Rotators – und gelegentlich der Blick aus dem Fenster.
|
||||
|
||||
Konfiguration und Portbelegung: [Konfiguration – PSTRotator-Einstellungen](de-Konfiguration#pstrotator-einstellungen-ab-v131-vollständig-konfigurierbar-ab-v140).
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Band-Upgrade-Hinweis nach einem Logeintrag
|
||||
|
||||
Meldet UCXLog oder Win-Test einen neuen Logeintrag mit Bandinformation, prüft KST4Contest, ob die gearbeitete Station noch ein weiteres gemeinsames Band anbietet.
|
||||
@@ -508,7 +608,7 @@ Die Bewertung wird für das normalisierte Basisrufzeichen vorgenommen. Ein Sked
|
||||
|
||||
### Wie werden mehrere SSIDs und Chat-Kategorien behandelt?
|
||||
|
||||
Aktive Rufzeichen wie `9A0BB-2`, `9A0BB-70`, `9A0BB-23` und `9A0BB-13` bleiben getrennte Chatmember. Dadurch können Nachrichten weiterhin an das vollständige Rufzeichen und die richtige Chat-Kategorie adressiert werden.
|
||||
Aktive vollständige sichtbare Rufzeichenvarianten wie `9A0BB-2`, `9A0BB-70`, `9A0BB-23` und `9A0BB-13` bleiben getrennte Chatmember. Dadurch können Nachrichten weiterhin an das vollständige Rufzeichen und die richtige Chat-Kategorie adressiert werden.
|
||||
|
||||
Worked-, Band-, NOT-QRV- und Score-Informationen beziehen sich dagegen auf das gemeinsame Basisrufzeichen `9A0BB`. Der Score wird deshalb einmal berechnet und auf alle aktiven Varianten übertragen. Die Benutzerliste kann mehrere getrennte Zeilen mit demselben Score enthalten; in der Prioritätsliste erscheint das Basisrufzeichen nur einmal.
|
||||
|
||||
@@ -605,16 +705,24 @@ Die Timeline ist eine Vorschau. AirScout-Daten können sich ändern, und ein ein
|
||||
|
||||
## Intervall-Beacon
|
||||
|
||||
KST4Contest kann wiederkehrende CQ-Nachrichten in den öffentlichen Chat senden. Beide Chat-Kategorien verwenden ein gemeinsames Intervall, besitzen aber jeweils einen eigenen Aktivierungsschalter und Nachrichtentext. Globale Variablen wie `MYQRG`, `SECONDQRG` oder `MYLOCATOR` werden unmittelbar vor jeder Aussendung aktualisiert.
|
||||
|
||||
KST4Contest kann wiederkehrende CQ-Nachrichten in den öffentlichen Chat senden. Der Beacon ist für längeres CQ-Rufen auf einer festen Frequenz gedacht: Die eigene QRG wird regelmäßig veröffentlicht, ohne dass derselbe Text von Hand wiederholt werden muss.
|
||||
|
||||
Der Beacon ist für längeres CQ-Rufen auf einer festen Frequenz gedacht. Beim Absuchen oder häufigen Wechseln der QRG sollte er ausgeschaltet werden, damit keine inzwischen falsche Frequenz verbreitet wird. Details: [Konfiguration – Beacon Settings](de-Konfiguration#beacon-settings-automatischer-beacon).
|
||||
Beide Chat-Kategorien verwenden ein gemeinsames Intervall, besitzen aber jeweils einen eigenen Aktivierungsschalter und Nachrichtentext. Der zweite Beacon wird nur gesendet, wenn die zweite Kategorie in derselben ON4KST-Sitzung aktiviert und vollständig synchronisiert ist.
|
||||
|
||||
Globale Variablen wie `MYQRG`, `SECONDQRG`, `MYLOCATOR` oder `MYQTF` werden unmittelbar vor jeder Aussendung neu aufgelöst. Eine zwischenzeitlich vom Logprogramm aktualisierte QRG kann dadurch bereits im nächsten Beacon erscheinen.
|
||||
|
||||
Vor dem Versand prüft KST4Contest den vollständig aufgelösten Nachrichtentext. Leere Nachrichten, Zeilenumbrüche, das Protokoll-Trennzeichen `|` und Texte mit mehr als 120 Zeichen werden nicht gesendet.
|
||||
|
||||
Beim Absuchen oder häufigen Wechseln der QRG sollte der Beacon ausgeschaltet werden. Eine automatisch verbreitete Frequenz ist nur dann hilfreich, wenn dort tatsächlich jemand hört und ruft.
|
||||
|
||||
Konfiguration, Timer-Verhalten und verfügbare Variablen: [Konfiguration – Beacon Settings](de-Konfiguration#beacon-settings-automatischer-beacon).
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Simplelogfile
|
||||
|
||||
Details: [Log-Synchronisation](de-Log-Synchronisation#methode-1-universal-file-based-callsign-interpreter-simplelogfile).
|
||||
Der Simplelogfile-Interpreter liest einmal pro Minute Rufzeichen aus einer ausgewählten Textdatei. Fehlt die Datei, legt KST4Contest sie an und zeigt einen nicht blockierenden Hinweis mit Pfad, Funktionstest und Contestprüfung. Details: [Log-Synchronisation](de-Log-Synchronisation#methode-1-universal-file-based-callsign-interpreter-simplelogfile).
|
||||
|
||||
---
|
||||
|
||||
@@ -904,4 +1012,4 @@ Die Prüfung verwendet immer den **primären Bildschirm**. Sie stellt nicht die
|
||||
|
||||
Die automatische Größenbegrenzung gilt derzeit außerdem nur für das Hauptfenster. Das Einstellungsfenster, das separate Cluster- und QSO-Monitorfenster sowie weitere Zusatzfenster verwenden weiterhin ihre jeweils gespeicherten Größen, ohne dieselbe zusätzliche Prüfung gegen den primären Bildschirm.
|
||||
|
||||
Im Klartext: Die Schutzfunktion verhindert vor allem, dass das zentrale Hauptfenster nach einem Wechsel auf einen kleineren Bildschirm unbenutzbar startet. Sie ist keine vollständige Verwaltung aller Fensterpositionen in einem wechselnden Mehrmonitor-Setup.
|
||||
Im Klartext: Die Schutzfunktion verhindert vor allem, dass das zentrale Hauptfenster nach einem Wechsel auf einen kleineren Bildschirm unbenutzbar startet. Sie ist keine vollständige Verwaltung aller Fensterpositionen in einem wechselnden Mehrmonitor-Setup.
|
||||
|
||||
@@ -52,7 +52,7 @@ Download, unterstützte Betriebssysteme und Installationswege sind im Kapitel [I
|
||||
|
||||
Dieses Handbuch unterscheidet zwischen der veröffentlichten Stable-Version und dem aktuellen Entwicklungsstand.
|
||||
|
||||
Die derzeit veröffentlichte Stable-Version ist **v1.41.1**. Funktionen oder Änderungen, die erst im Entwicklungsstand für v1.42 enthalten sind, werden ausdrücklich als **Nightly / v1.42** gekennzeichnet. Fehlt eine solche Kennzeichnung, bezieht sich die Beschreibung auf die Stable-Version.
|
||||
Die derzeit veröffentlichte Stable-Version ist **v1.42.0**. Funktionen oder Änderungen, die erst mit einer bestimmten Version hinzugekommen sind, werden ausdrücklich als **ab v1.42** gekennzeichnet. Funktionen, die es nur im aktuellen Entwicklungsstand gibt, sind als **Nightly** gekennzeichnet. Fehlt eine solche Kennzeichnung, bezieht sich die Beschreibung auf die Stable-Version.
|
||||
|
||||
- [Stable, Beta und Nightly herunterladen](https://kst4contest.hamradioonline.de/download/)
|
||||
- [Veröffentlichte GitHub Releases](https://github.com/praktimarc/kst4contest/releases)
|
||||
@@ -68,6 +68,7 @@ Für einen Contest ist grundsätzlich die Stable-Version zu empfehlen. Nightly-B
|
||||
|---|---|
|
||||
| [Installation](de-Installation) | ON4KST-Account, Download, Installation und Updates |
|
||||
| [Konfiguration](de-Konfiguration) | Login, Station, Bänder, Benutzeroberfläche und externe Schnittstellen |
|
||||
| [Contest-Workflow](de-Contest-Workflow) | Vorstartprüfung, CQ-Betrieb, Kandidatenauswahl, Skeds, Logeintrag und Bandwechsel |
|
||||
| [Log-Synchronisation](de-Log-Synchronisation) | Simplelogfile, UCXLog, N1MM+, QARTest, DXLog.net und Win-Test |
|
||||
| [AirScout-Integration](de-AirScout-Integration) | Verbindung zu AirScout und Auswertung von Aircraft-Scatter-Zeiten |
|
||||
| [DX-Cluster-Server](de-DX-Cluster-Server) | Übergabe erkannter Möglichkeiten an das Logprogramm |
|
||||
@@ -123,6 +124,6 @@ Besonderer Dank gilt:
|
||||
- Konrad Neitzel (DC9DJ) für Unterstützung bei der Projektstruktur,
|
||||
- Andreas (DO5ALF), Webmaster von funkerportal.de,
|
||||
- Franz van Velzen (PE0WGA) für Tests sowie
|
||||
- DN9APW (Philipp Wagner), der seit Mai 2025 an der Entwicklung und den CI/CD-Pipelines mitarbeitet.
|
||||
- DN9APW (Philipp Wagner), der seit Mai 2026 an der Entwicklung und den CI/CD-Pipelines mitarbeitet.
|
||||
|
||||
Ebenso wichtig sind die Fehlermeldungen, Tests und konkreten Betriebserfahrungen aller weiteren Nutzer. Eine genaue Beschreibung eines reproduzierbaren Problems hilft dem Projekt meistens mehr als ein allgemeines „läuft gut“ – auch wenn Letzteres natürlich angenehmer zu lesen ist.
|
||||
|
||||
@@ -267,7 +267,19 @@ Für Apple-Silicon-Macs wird das Paket mit `arm64` benötigt. Für Intel-Macs is
|
||||
3. Ziehe `KST4Contest.app` in den Ordner **Programme**.
|
||||
4. Starte KST4Contest aus dem Programme-Ordner oder über das Launchpad.
|
||||
|
||||
Die Anwendung ist derzeit nicht von Apple notarisiert. macOS kann den ersten Start deshalb blockieren.
|
||||
Ab Version 1.42 sind die macOS-Pakete mit einer Apple Developer ID signiert und von Apple notarisiert. Der erste Start funktioniert damit per Doppelklick, ohne Umweg über das Kontextmenü und ohne Sicherheitsabfrage. Das Notarisierungsticket liegt in der DMG-Datei selbst, sodass die Prüfung auch ohne Internetverbindung gelingt.
|
||||
|
||||
Wer nachsehen möchte, ob ein heruntergeladenes Paket wirklich signiert ist, kann das im Terminal prüfen:
|
||||
|
||||
```bash
|
||||
spctl --assess --type open --context context:primary-signature -v KST4Contest-v<Version>-macos-arm64.dmg
|
||||
```
|
||||
|
||||
Erwartet wird `accepted` zusammen mit `source=Notarized Developer ID`.
|
||||
|
||||
### Versionen bis einschließlich 1.41.1
|
||||
|
||||
Ältere Pakete sind nicht notarisiert. macOS blockiert den ersten Start deshalb.
|
||||
|
||||
Falls die Anwendung aus dem offiziellen GitHub Release stammt:
|
||||
|
||||
@@ -408,7 +420,9 @@ Starte die Datei anschließend aus einem Terminal. Fehlermeldungen sind dort mei
|
||||
|
||||
### macOS blockiert die Anwendung
|
||||
|
||||
Verwende die unter [Installation unter macOS](#installation-unter-macos) beschriebene Funktion **Öffnen** im Kontextmenü. Prüfe vorher, ob die DMG-Datei aus dem offiziellen GitHub Release stammt.
|
||||
Ab Version 1.42 sollte das nicht mehr vorkommen, da die Pakete signiert und notarisiert sind. Tritt die Blockade trotzdem auf, ist die DMG-Datei meist unvollständig heruntergeladen oder nachträglich verändert worden. Lade sie in dem Fall erneut aus dem offiziellen GitHub Release.
|
||||
|
||||
Bei älteren Versionen ist die Blockade zu erwarten. Verwende dort die unter [Installation unter macOS](#installation-unter-macos) beschriebene Funktion **Öffnen** im Kontextmenü.
|
||||
|
||||
### Das Problem bleibt bestehen
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Nach dem ersten Start öffnet sich das **Einstellungsfenster** – dieses ist der zentrale Ausgangspunkt für alle Konfigurationen. Es empfiehlt sich, das Einstellungsfenster während des Betriebs geöffnet zu lassen (z. B. um den Beacon schnell ein- und auszuschalten).
|
||||
|
||||
> **Wichtig**: Nach jeder Änderung unbedingt **„Save Settings"** klicken! Die Einstellungen werden unter Linux in `~/.praktikst/preferences.xml` und unter Windows in `%USERPROFILE%\.praktikst\preferences.xml` (bzw. `C:\Users\<Benutzername>\.praktikst\preferences.xml`) gespeichert. Ab v1.21 werden auch Fenstergrößen und Divider-Positionen beim Speichern gesichert.
|
||||
> **Wichtig**: Nach jeder Änderung unbedingt **„Save Settings"** klicken! Die Einstellungen werden unter Linux und macOS in `~/.praktiKST/preferences.xml` und unter Windows in `%USERPROFILE%\.praktiKST\preferences.xml` (bzw. `C:\Users\<Benutzername>\.praktiKST\preferences.xml`) gespeichert. Ab v1.21 werden auch Fenstergrößen und Divider-Positionen beim Speichern gesichert.
|
||||
|
||||
---
|
||||
|
||||
@@ -14,14 +14,15 @@ Nach dem ersten Start öffnet sich das **Einstellungsfenster** – dieses ist de
|
||||
|
||||
### Login und Chat-Kategorien
|
||||
|
||||
Hier werden die Zugangsdaten für den ON4KST-Chat eingetragen (Rufzeichen und Passwort).
|
||||
Zudem wird die **primäre Chat-Kategorie** (z. B. IARU Region 1 VHF/Microwave) ausgewählt.
|
||||
Hier werden das lokale Login-Rufzeichen und das Passwort für den ON4KST-Chat eingetragen. Zudem wird die **primäre Chat-Kategorie** (z. B. IARU Region 1 VHF/Microwave) ausgewählt.
|
||||
|
||||
Mit der Option für einen **zweiten Chat** (Multi-Channel-Login) kann man sich gleichzeitig in eine weitere Kategorie (z. B. UHF/SHF) einloggen. Beide Chats werden dann parallel überwacht. Hier kann optional auch ein abweichender Login-Name für den zweiten Chat vergeben werden (nützlich für Opposite Station Multi-Callsign Logging).
|
||||
Mit der Option für einen **zweiten Chat** (Multi-Channel-Login) wird eine weitere Kategorie (z. B. UHF/SHF) per Single Sign-on in derselben ON4KST-TCP-Sitzung aktiviert. KST4Contest öffnet dafür keine zweite TCP-Verbindung und verwendet weder ein zweites lokales Login-Rufzeichen noch ein zweites Passwort.
|
||||
|
||||
**Name in Chat 2** konfiguriert ausschließlich das sichtbare, kategoriebasierte Namensfeld der zweiten Kategorie. Es ist weder ein zweites Login noch ein Nachrichtenempfänger. Das sichtbare Namensfeld, der Nachrichtenkontext, die QRG und der Beacon bleiben für beide Kategorien getrennt. **Opposite Station Multi-Callsign Login-Tagging** bezieht sich dagegen ausschließlich auf Gegenstationen, die mit mehreren vollständigen sichtbaren Rufzeichenvarianten im Chat erscheinen.
|
||||
|
||||
### Rufzeichen und Locator
|
||||
|
||||
Eigenes Rufzeichen und Maidenhead-Locator (6-stellig, z. B. `JN49IJ`) eintragen. Diese Werte werden für Distanz- und Richtungsberechnungen benötigt.
|
||||
Eigenes Rufzeichen und Maidenhead-Locator (6-stellig, z. B. `JN49IJ`) eintragen. Der Locator ist kein Bestandteil der Authentifizierung. ON4KST verwendet ihn gemeinsam für beide Kategorien der einen TCP-Sitzung; KST4Contest benötigt ihn außerdem für Distanz- und Richtungsberechnungen.
|
||||
|
||||
### Aktivierte Bänder
|
||||
|
||||
@@ -64,43 +65,7 @@ Der Wert sollte zum eigenen Stationsaufbau und zum vorgesehenen Contestbetrieb p
|
||||
|
||||
### Streckenanalyse und Link-Budget
|
||||
|
||||
Die Streckenanalyse der Stationskarte verwendet einige Angaben aus der Stationskonfiguration. Diese Werte beschreiben das eigene Stationssetup und – soweit keine individuellen Daten der Gegenstation vorliegen – ein angenommenes Setup der Gegenstation.
|
||||
|
||||
Folgende Einstellungen werden berücksichtigt:
|
||||
|
||||
- **Own antenna height AGL [m]** gibt die Höhe der eigenen Antenne über dem lokalen Gelände an. Die Angabe bezieht sich auf *Above Ground Level* und nicht auf die Höhe über dem Meeresspiegel. KST4Contest addiert diesen Wert zur Geländehöhe am eigenen Standort.
|
||||
- **Own TX power [W]** gibt die verwendete Sendeleistung der eigenen Station in Watt an.
|
||||
- **Own ant. gain [dBi]** gibt den Antennengewinn der eigenen Station in dBi an.
|
||||
- **DX OM TX power [W]** gibt die für die Gegenstation angenommene Sendeleistung in Watt an.
|
||||
- **DX OM ant. gain [dBi]** gibt den für die Gegenstation angenommenen Antennengewinn in dBi an.
|
||||
|
||||
Für die Antennenhöhe der Gegenstation verwendet KST4Contest derzeit einen festen Wert von 10 m über dem lokalen Gelände. Die Leistungs- und Antennendaten der Gegenstation sind globale Annahmen. Sie ersetzen keine individuell bekannten Stationsdaten, ermöglichen aber eine einheitliche Abschätzung, wenn keine genaueren Informationen vorliegen.
|
||||
|
||||
Die Antennengewinne müssen in dBi angegeben werden. Falls ein Wert in dBd vorliegt, kann er näherungsweise wie folgt umgerechnet werden:
|
||||
|
||||
`dBi = dBd + 2.15`
|
||||
|
||||
Auch die aktuelle Antennenrichtung, die konfigurierte Strahlbreite, das maximale QRB und die für die eigene Station aktivierten Bänder beeinflussen die Darstellung oder Auswertung auf der Stationskarte. Die aktuelle QTF und die Strahlbreite bestimmen beispielsweise den eingezeichneten Antennensektor und die Hervorhebung von Stationen innerhalb dieses Bereichs.
|
||||
|
||||
Für die Berücksichtigung der Erdkrümmung verwendet die Streckenanalyse einen festen Faktor von `k = 4/3` für den effektiven Erdradius. Das ist eine übliche Näherung für eine durchschnittliche troposphärische Refraktion. Tatsächliche Ausbreitungsbedingungen können davon deutlich abweichen.
|
||||
|
||||
Das Link-Budget berücksichtigt unter anderem:
|
||||
|
||||
- die Entfernung zwischen beiden Stationen,
|
||||
- die verwendete Frequenz,
|
||||
- die konfigurierte Sendeleistung,
|
||||
- die Antennengewinne,
|
||||
- geschätzte Speiseleitungsverluste,
|
||||
- den Freiraumverlust sowie
|
||||
- eine grobe Zusatzdämpfung durch Hindernisse im Streckenprofil.
|
||||
|
||||
Die daraus berechnete Empfangsleistung und SSB- beziehungsweise CW-Marge sind technische Abschätzungen. Sie sollen dabei helfen, mögliche Verbindungen einzuordnen. Sie sind keine vollständige Feldstärkeprognose und können insbesondere aktuelle Wetterbedingungen, lokale Abschattungen, Mehrwegeausbreitung oder andere nicht bekannte Stationsparameter nicht vollständig berücksichtigen.
|
||||
|
||||
---
|
||||
|
||||
### Streckenanalyse und Link-Budget
|
||||
|
||||
Die Stationskarte verwendet mehrere Werte aus dem Reiter **Station**, um das Geländeprofil und das Link-Budget zur ausgewählten Gegenstation zu berechnen.
|
||||
Die Stationskarte verwendet mehrere Werte aus dem Reiter **Station**, um das Geländeprofil und das Link-Budget zur ausgewählten Gegenstation zu berechnen. Die Angaben zum eigenen Stationsaufbau sollten deshalb möglichst realistisch sein. Bei der Gegenstation handelt es sich dagegen um globale Annahmen, solange keine genaueren Daten vorliegen.
|
||||
|
||||
| Einstellung | Verwendung |
|
||||
|---|---|
|
||||
@@ -110,35 +75,43 @@ Die Stationskarte verwendet mehrere Werte aus dem Reiter **Station**, um das Gel
|
||||
| **DX OM TX power W** | Angenommene Sendeleistung der Gegenstation in Watt |
|
||||
| **DX OM ant. gain dBi** | Angenommener Antennengewinn der Gegenstation in dBi |
|
||||
|
||||
**AGL** bedeutet „above ground level“. Trage hier nicht die Höhe über dem Meeresspiegel ein. Die Geländehöhe am eigenen Standort stammt bereits aus dem abgerufenen Höhenprofil; die konfigurierte Antennenhöhe wird zu diesem Wert addiert.
|
||||
**AGL** bedeutet *Above Ground Level*. Trage hier nicht die Höhe über dem Meeresspiegel ein. Die Geländehöhe am eigenen Standort stammt bereits aus dem Höhenprofil; KST4Contest addiert die konfigurierte Antennenhöhe zu diesem Wert.
|
||||
|
||||
Für die Antennenhöhe der Gegenstation verwendet KST4Contest derzeit einen festen Standardwert von 10 Metern über Grund. Eine stationsbezogene Antennenhöhe wird im ON4KST-Chat nicht übertragen.
|
||||
Für die Gegenstation verwendet KST4Contest derzeit eine feste Antennenhöhe von 10 Metern über dem lokalen Gelände. Sendeleistung und Antennengewinn der Gegenstation stammen aus den beiden **DX OM**-Feldern. Diese Werte sind bewusst nur Annahmen: Der ON4KST-Chat überträgt weder die tatsächliche Antennenhöhe noch die vollständigen Stationsdaten der Gegenstation.
|
||||
|
||||
Antennengewinne müssen in `dBi` eingetragen werden. Liegt ein Wert in `dBd` vor, gilt:
|
||||
Antennengewinne müssen in `dBi` eingetragen werden. Liegt ein Wert in `dBd` vor, muss vor der Eingabe `2.15 dB` addiert werden:
|
||||
|
||||
```text
|
||||
dBi = dBd + 2,15 dB
|
||||
dBi = dBd + 2.15 dB
|
||||
```
|
||||
|
||||
Die aktuelle QTF, der konfigurierte Antennen-Öffnungswinkel und das Standard-Maximum-QRB beeinflussen die Darstellung der Stationskarte. Die für die eigene Station aktivierten Bänder und die für die Gegenstation hergeleitete Frequenz wirken sich zusätzlich auf die Auswahl der Analysefrequenz aus.
|
||||
|
||||
Für das Geländeprofil berücksichtigt KST4Contest die Antennenhöhen, das Höhenmodell und die Erdkrümmung mit einem festen effektiven Erdradiusfaktor von `k = 4/3`. Das Link-Budget verwendet außerdem:
|
||||
|
||||
## Server-Einstellungen (ab v1.31)
|
||||
- die Entfernung und die aktuelle Analysefrequenz,
|
||||
- die Sendeleistungen und Antennengewinne beider Stationen,
|
||||
- frequenzabhängig geschätzte Speiseleitungsverluste,
|
||||
- die Freiraumdämpfung und
|
||||
- eine grobe zusätzliche Dämpfung durch das maßgebliche Hindernis im Geländeprofil.
|
||||
|
||||
Der Chat-Server-DNS und -Port sind in den Preferences konfigurierbar:
|
||||
Die Berechnung erfolgt für beide Übertragungsrichtungen. Für die gemeinsame SSB- beziehungsweise CW-Marge ist die ungünstigere Richtung maßgeblich. Zu optimistische Leistungs- oder Antennenwerte verbessern daher zwar das angezeigte Ergebnis, nicht aber den realen Funkweg.
|
||||
|
||||
- **Server-DNS**: Standard `www.on4kst.org` (ab v1.31 geändert von `www.on4kst.info`).
|
||||
- **Port**: Standardport des ON4KST-Servers.
|
||||
|
||||
Eine Änderung ist nur notwendig, wenn der Server umzieht oder ein alternativer Endpunkt genutzt wird.
|
||||
Die Werte bleiben technische Abschätzungen. Aktuelle Ausbreitungsbedingungen, lokale Abschattungen, Bewuchs, Gebäude, Störungen und nicht bekannte Stationsparameter können das tatsächliche Ergebnis deutlich verändern. Bedienung, Frequenzauswahl und Grenzen der Berechnung sind unter [Stationskarte und Streckenanalyse](de-Funktionen#stationskarte-und-streckenanalyse-ab-v141) beschrieben.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## Log-Sync-Einstellungen
|
||||
|
||||
Drei Methoden stehen zur Verfügung, um gearbeitete Stationen automatisch zu markieren. Details: [Log-Synchronisation](de-Log-Synchronisation).
|
||||
|
||||
### Universal File Based Callsign Interpreter (Simplelogfile)
|
||||
|
||||
Interpretiert beliebige Log-Dateien per Regex nach Rufzeichen-Mustern. Keine Bandinformation möglich. Geeignet als Fallback oder für nicht direkt unterstützte Logprogramme.
|
||||
Liest die ausgewählte Textdatei einmal pro Minute mit einem fest eingebauten Rufzeichenmuster. Die Treffer werden auf Basisrufzeichen normalisiert und setzen für alle aktiven Varianten nur den globalen Worked-Status. Band- und Locatorinformationen können daraus nicht abgeleitet werden. Die Funktion eignet sich als Fallback für Logprogramme ohne unterstützte Netzwerkschnittstelle. Vor der Verwendung sollte deshalb geprüft werden, ob das eingesetzte Logprogramm bereits eine der unterstützten Netzwerkschnittstellen bereitstellt.
|
||||
|
||||
Existiert die ausgewählte Datei noch nicht, legt KST4Contest sie an und zeigt einmalig den vollständigen Pfad sowie Hinweise zum Funktions- und Contesttest. Die Datei ist selbst die dauerhafte Quelle; aus ihr abgeleitete Worked-Markierungen werden nicht in SQLite gespeichert und nicht automatisch für einen neuen Contest zurückgesetzt.
|
||||
|
||||
### Netzwerk-Listener für QSO-UDP-Broadcast
|
||||
|
||||
@@ -152,18 +125,197 @@ Dedizierter Netzwerk-Erkenner für Win-Test. KST4Contest empfängt und verarbeit
|
||||
|
||||
## TRX-Sync-Einstellungen
|
||||
|
||||
Empfängt die aktuelle Frequenz des Transceivers vom Logprogramm via UDP. Ermöglicht die automatische Befüllung der Variable `MYQRG`. Nützlich für:
|
||||
Die TRX-Synchronisation übernimmt die aktuelle Frequenz aus dem Logprogramm und stellt sie in KST4Contest als eigene QRG der ersten Chat-Kategorie bereit. QSO- und Frequenzsynchronisation verwenden teilweise denselben UDP-Empfänger, sind funktional aber voneinander getrennt: Ein empfangenes `RadioInfo`-Paket markiert keine Station als gearbeitet, und ein QSO-Paket ändert nicht automatisch die eigene QRG.
|
||||
|
||||
- Schnelles Einfügen der eigenen QRG in Chat-Nachrichten.
|
||||
- Automatische CQ-Baken mit aktueller Frequenz.
|
||||

|
||||
|
||||
> **Hinweis für Multi-Setup**: Wenn zwei Logprogramme an zwei Computern betrieben werden, aber nur eine KST4Contest-Instanz, darf nur ein Logprogramm die Frequenzpakete senden. KST4Contest kann nicht zwischen den Quellen unterscheiden.
|
||||
### Verfügbare QRG-Quellen
|
||||
|
||||
| Quelle | Aktivierung | Verhalten |
|
||||
|---|---|---|
|
||||
| **Allgemeiner RadioInfo-Listener** | `Update MYQRG from RadioInfo messages received on the shared log-sync port` | Verarbeitet kompatible `RadioInfo`-Pakete auf dem gemeinsam mit der QSO-Synchronisation verwendeten UDP-Port. Der Standardport ist `12060`. |
|
||||
| **Win-Test STATUS** | `Win-Test STATUS QRG Sync` | Verarbeitet die Haupt- oder Pass-Frequenz aus nativen Win-Test-`STATUS`-Paketen. Der Win-Test-Listener verwendet seinen separat konfigurierten Port, standardmäßig `9871`. |
|
||||
| **Manuelle Eingabe** | Beide automatischen QRG-Quellen deaktivieren | Die eigene QRG kann im Hauptfenster von Hand eingetragen werden. |
|
||||
|
||||
Der allgemeine Listener ist für Logprogramme vorgesehen, die kompatible `RadioInfo`-Pakete senden. Dazu gehören – abhängig von deren jeweiliger Konfiguration – UCXLog, N1MM+, QARTest und DXLog.net. QSO- und `RadioInfo`-Pakete verwenden denselben unter **Log sync** konfigurierten Port. Dort wird jedoch getrennt festgelegt, ob KST4Contest QSO-Informationen, TRX-Informationen oder beide Paketarten verarbeitet. Eine automatische QRG-Quelle muss aktiviert sein und tatsächlich gültige Pakete liefern; die Aktivierung allein aktualisiert `MYQRG` nicht.
|
||||
|
||||
Wird der gemeinsame UDP-Port geändert, muss KST4Contest neu gestartet werden. Eine reine Änderung der Checkboxen wird dagegen sofort berücksichtigt.
|
||||
|
||||
### Welche QRG wird aktualisiert?
|
||||
|
||||
Beide automatischen Quellen aktualisieren ausschließlich `MYQRG`. Das ist die eigene QRG der ersten beziehungsweise primären Chat-Kategorie.
|
||||
|
||||
Bei aktiviertem zweiten Chat bleibt dessen QRG davon unabhängig. Sie wird nicht aus den empfangenen TRX-Paketen abgeleitet und steht als `SECONDQRG` zur Verfügung. Dadurch kann beispielsweise die erste Kategorie automatisch der Frequenz des Logprogramms folgen, während für die zweite Kategorie eine eigene QRG von Hand eingetragen wird.
|
||||
|
||||
Sobald mindestens eine automatische QRG-Quelle aktiviert ist und einen gültigen Wert liefert, folgt das QRG-Feld der ersten Kategorie im Hauptfenster der empfangenen Frequenz. Bleiben die erwarteten Pakete aus, sollte die Schnittstelle vor dem Contest geprüft werden. Eine manuelle Eingabe in dieses Feld ist möglich, wenn sowohl der allgemeine RadioInfo-Listener als auch die Win-Test-STATUS-Synchronisation deaktiviert sind.
|
||||
|
||||
### Haupt- oder Pass-Frequenz aus Win-Test
|
||||
|
||||
Standardmäßig verwendet KST4Contest die Hauptfrequenz des empfangenen Win-Test-`STATUS`-Pakets.
|
||||
|
||||
Die Option `Use pass frequency from Win-Test STATUS` verwendet stattdessen die im Paket enthaltene Pass-Frequenz. Das ist beispielsweise sinnvoll, wenn Win-Test im Split-Betrieb eine abweichende Frequenz führt und genau diese im Chat als Arbeitsfrequenz veröffentlicht werden soll.
|
||||
|
||||
Enthält das Paket keine gültige Pass-Frequenz, fällt KST4Contest automatisch auf die Hauptfrequenz zurück. Eine fehlende Pass-Frequenz löscht daher weder `MYQRG` noch ersetzt sie den Wert durch eine offensichtlich falsche Zahl.
|
||||
|
||||
Die Frequenzen werden einheitlich im KST4Contest-Format dargestellt, beispielsweise:
|
||||
|
||||
```text
|
||||
50.300.00
|
||||
144.300.00
|
||||
1296.100.00
|
||||
10368.100.00
|
||||
```
|
||||
|
||||
Die Werte werden erst beim Auflösen des Nachrichtentextes eingesetzt. Ändert das Logprogramm zwischen zwei Beacon-Läufen die Frequenz, verwendet die nächste Nachricht bereits den aktualisierten Wert.
|
||||
|
||||
Die eigene QRG kann außerdem als Fallback für die Übergabe eines Skeds an Win-Test verwendet werden. Das geschieht nur, wenn die QRG auswertbar ist und zum ausdrücklich ausgewählten Sked-Band gehört. Einzelheiten stehen unter [Log-Synchronisation](de-Log-Synchronisation#skeds-an-win-test-übergeben).
|
||||
|
||||
Weitere Informationen zu den Textvariablen: [Makros und Variablen](de-Makros-und-Variablen#variablen).
|
||||
|
||||
### Mehrere Logger oder Funkgeräte
|
||||
|
||||
Alle aktivierten QRG-Quellen schreiben in denselben Wert `MYQRG`. KST4Contest ordnet eingehende `RadioInfo`- oder `STATUS`-Pakete derzeit weder einem bestimmten Funkgerät noch einer Chat-Kategorie zu.
|
||||
|
||||
Sind der allgemeine RadioInfo-Listener und die Win-Test-Synchronisation gleichzeitig aktiviert, bestimmt deshalb das zuletzt verarbeitete Paket die angezeigte QRG. Dasselbe gilt, wenn mehrere Logger ihre Frequenzpakete an dieselbe KST4Contest-Instanz senden.
|
||||
|
||||
Für ein Setup mit mehreren Funkgeräten gilt daher:
|
||||
|
||||
- QSO-Pakete dürfen von mehreren Loggern empfangen werden.
|
||||
- Frequenzpakete sollten nur von der Quelle gesendet werden, die `MYQRG` tatsächlich steuern soll.
|
||||
- In einem Win-Test-Netzwerk sollte zusätzlich der Stationsfilter verwendet werden.
|
||||
- Werden zwei vollständig unabhängige QRG-Synchronisationen benötigt, sind zwei getrennte KST4Contest-Instanzen die eindeutigere Lösung.
|
||||
|
||||
Anders ausgedrückt: Mehrere Worked-Quellen lassen sich sinnvoll zusammenführen. Mehrere gleichzeitig sendende Frequenzquellen erzeugen dagegen keine zusätzliche Information, sondern lediglich einen Wettbewerb darum, welches Paket zuletzt angekommen ist.
|
||||
|
||||
Nach Abschluss der Konfiguration **Save Settings** verwenden.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## AirScout-Einstellungen
|
||||
|
||||
Konfiguration der Schnittstelle zu AirScout für die Flugzeug-Scatter-Erkennung. Details: [AirScout-Integration](de-AirScout-Integration).
|
||||
Im Reiter **AirScout** wird die UDP-Verbindung zwischen KST4Contest und AirScout eingerichtet. KST4Contest fordert dort keine allgemeinen Flugzeugdaten an, sondern übermittelt die aktuell relevanten Stationspfade. AirScout berechnet die dazu passenden Flugzeuge und sendet das Ergebnis an die anfragende KST4Contest-Instanz zurück.
|
||||
|
||||
Vorausgesetzt wird AirScout `0.9.9.5` oder neuer.
|
||||
|
||||
{ width=85% }
|
||||
|
||||
### Einstellungen der UDP-Verbindung
|
||||
|
||||
| Einstellung | Standardwert | Verwendung |
|
||||
|---|---:|---|
|
||||
| **Enable AirScout UDP integration** | deaktiviert | Aktiviert das Senden von AirScout-Anfragen und die Verarbeitung der Antworten |
|
||||
| **AirScout server identifier** | `AS` | Logischer Name der angesprochenen AirScout-Instanz |
|
||||
| **KST4Contest client identifier** | `KST` | Logischer Name dieser KST4Contest-Instanz |
|
||||
| **AirScout UDP port** | `9872` | Gemeinsamer UDP-Port für Anfragen und Antworten |
|
||||
| **Select AirScout frequency automatically per station** | aktiviert | Ermittelt Band und Frequenz für jede Gegenstation aus dem aktuellen Stationskontext |
|
||||
| **Forced AirScout band value** | `1440000` | Verwendet bei deaktivierter Automatik einen festen AirScout-Bandwert für alle Stationen |
|
||||
|
||||
Ist **Enable AirScout UDP integration** deaktiviert, sendet KST4Contest keine AirScout-Anfragen und verwirft eingehende AirScout-Antworten. Der UDP-Empfänger kann trotzdem gebunden bleiben, damit sich die Funktion während einer laufenden Verbindung wieder einschalten lässt.
|
||||
|
||||
KST4Contest verwendet für ausgehende AirScout-Pakete die Broadcast-Adresse `255.255.255.255`. Eine Ziel-IP wird deshalb nicht separat konfiguriert. AirScout und KST4Contest müssen den verwendeten UDP-Broadcast empfangen können; Router leiten einen solchen Broadcast normalerweise nicht in ein anderes Netz weiter. Bei Problemen sollten daher zuerst der UDP-Port, die lokale Firewall und die Netzzuordnung geprüft werden.
|
||||
|
||||
### Automatische Bandauswahl pro Station
|
||||
|
||||
**Auto per station** ist die empfohlene Einstellung. KST4Contest verwendet dann nicht einen festen Bandwert für alle Gegenstationen, sondern leitet eine geeignete Frequenz aus den verfügbaren Informationen ab.
|
||||
|
||||
Die Quellen werden in folgender Reihenfolge ausgewertet:
|
||||
|
||||
1. die zuletzt erkannte, höchstens 30 Minuten alte QRG der Gegenstation,
|
||||
2. eine eindeutige vollständige QRG im Namensfeld eines aktiven Chat-Eintrags,
|
||||
3. eindeutige Bandangaben im Namensfeld,
|
||||
4. 432 MHz, wenn dieselbe Station gleichzeitig in der VHF/UHF- und Microwave-Kategorie aktiv ist und 432 MHz für die eigene Station aktiviert wurde,
|
||||
5. das niedrigste für die eigene Station aktivierte Band, das zur unterstützten Chat-Kategorie passt.
|
||||
|
||||
Aktive Chat-Varianten desselben Basisrufzeichens werden gemeinsam ausgewertet. Die Einträge `CALLSIGN`, `CALLSIGN-2` und `CALLSIGN-432` können dadurch gemeinsam zur Bandherleitung beitragen, bleiben für die Nachrichtenverarbeitung aber getrennte Chat-Teilnehmer.
|
||||
|
||||
Nur Bänder, die unter **My station uses …** aktiviert wurden, kommen für die automatische Auswahl infrage. Ein manuell gesetztes NOT-QRV-Kennzeichen schließt das betreffende Band aus und hat Vorrang vor automatisch erkannten QRG- oder Namensinformationen.
|
||||
|
||||
Unterstützt werden die Chat-Kategorien für 50/70 MHz, VHF/UHF, Microwave und EME/JT65. Andere ON4KST-Kategorien werden für die AirScout-Bandherleitung ignoriert. Kann keine ausreichend belastbare Frequenz bestimmt werden, sendet KST4Contest für diese Station keine Anfrage. Ein beliebiger Rückfall auf 144 MHz würde zwar ein syntaktisch vollständiges Paket erzeugen, aber nicht zwangsläufig eine sinnvolle Berechnung.
|
||||
|
||||
Die automatische AirScout-Auswahl verwendet dieselbe Herleitung wie die interne Streckenanalyse. Damit bewerten beide Funktionen den Stationspfad auf derselben fachlichen Grundlage.
|
||||
|
||||
### Festes AirScout-Band
|
||||
|
||||
Wird **Auto per station** deaktiviert, verwendet KST4Contest den unter **Forced AirScout band value** eingetragenen Wert für alle Stationen.
|
||||
|
||||
Der Wert wird in der von der AirScout-UDP-Schnittstelle verwendeten Einheit eingetragen:
|
||||
|
||||
| Band | AirScout-Wert |
|
||||
|---|---:|
|
||||
| 50 MHz | `500000` |
|
||||
| 70 MHz | `700000` |
|
||||
| 144 MHz | `1440000` |
|
||||
| 432 MHz | `4320000` |
|
||||
| 1296 MHz | `12960000` |
|
||||
| 2320 MHz | `23200000` |
|
||||
| 3400 MHz | `34000000` |
|
||||
| 5760 MHz | `57600000` |
|
||||
| 10368 MHz | `103680000` |
|
||||
| 24048 MHz | `240480000` |
|
||||
|
||||
Im festen Modus wird weder die zuletzt erkannte QRG noch das im Namen genannte Band der Gegenstation berücksichtigt. Diese Einstellung ist deshalb hauptsächlich für einen eindeutig auf ein Band begrenzten Stationsbetrieb oder zur Fehlersuche sinnvoll.
|
||||
|
||||
### Server- und Client-Identifier
|
||||
|
||||
Die Identifier gehören zum AirScout-Protokoll und sind keine DNS-Namen oder IP-Adressen.
|
||||
|
||||
Ausgehende Anfragen enthalten zunächst den Client- und anschließend den Server-Identifier:
|
||||
|
||||
```text
|
||||
"KST" "AS"
|
||||
```
|
||||
|
||||
AirScout antwortet in umgekehrter Reihenfolge:
|
||||
|
||||
```text
|
||||
"AS" "KST"
|
||||
```
|
||||
|
||||
KST4Contest verarbeitet eine Antwort nur, wenn beide Identifier exakt mit der aktuellen Konfiguration übereinstimmen. Der Vergleich unterscheidet zwischen Groß- und Kleinschreibung.
|
||||
|
||||
Die Identifier dürfen nicht leer sein und keine Anführungszeichen oder Zeilenumbrüche enthalten.
|
||||
|
||||
Werden mehrere KST4Contest-Instanzen im selben Netz betrieben, sollte jede einen eigenen Client-Identifier erhalten, beispielsweise:
|
||||
|
||||
```text
|
||||
KST-144
|
||||
KST-432
|
||||
```
|
||||
|
||||
Bei mehreren AirScout-Instanzen müssen zusätzlich unterschiedliche Server-Identifier verwendet werden. Dadurch wird verhindert, dass die Antwort für einen Arbeitsplatz von einer anderen KST4Contest-Instanz verarbeitet wird.
|
||||
|
||||
### Welche Stationen werden angefragt?
|
||||
|
||||
KST4Contest startet die erste periodische AirScout-Abfrage ungefähr zehn Sekunden nach dem Aufbau der Chat-Verbindung. Weitere Abfragen folgen im Abstand von 60 Sekunden.
|
||||
|
||||
Eine aktive Station wird nur berücksichtigt, wenn:
|
||||
|
||||
- ein verwendbares Rufzeichen vorhanden ist,
|
||||
- ein Locator vorhanden ist,
|
||||
- die Entfernung berechnet werden konnte,
|
||||
- die Entfernung kleiner als das konfigurierte **Maximum-QRB** ist und
|
||||
- ein verwendbares Band bestimmt werden konnte.
|
||||
|
||||
Mehrere aktive Chat-Einträge desselben Basisrufzeichens erzeugen nicht für jeden Suffix eine eigene identische Pfadberechnung. Die zurückgegebenen AirScout-Informationen werden anschließend wieder den passenden aktiven Chat-Varianten zugeordnet.
|
||||
|
||||
Die Auswahl begrenzt nicht nur den Netzwerkverkehr. Sie verhindert außerdem, dass AirScout dauerhaft Pfade berechnet, die außerhalb des für die eigene Station vorgesehenen Arbeitsbereichs liegen.
|
||||
|
||||
### Übernahme geänderter Einstellungen
|
||||
|
||||
Folgende Änderungen werden nach Verlassen des Eingabefeldes beziehungsweise Betätigen der Checkbox sofort für neue Pakete verwendet:
|
||||
|
||||
- Aktivierung oder Deaktivierung der AirScout-Integration,
|
||||
- Server-Identifier,
|
||||
- Client-Identifier,
|
||||
- automatische oder feste Bandauswahl und
|
||||
- fester Bandwert.
|
||||
|
||||
Nach einer Änderung des UDP-Ports muss die Chat-Verbindung getrennt und neu aufgebaut oder KST4Contest neu gestartet werden. Der bereits laufende UDP-Empfänger bleibt sonst weiterhin an den vorherigen Port gebunden.
|
||||
|
||||
Zum dauerhaften Speichern anschließend **Save Settings** verwenden.
|
||||
|
||||
Die Einrichtung der AirScout-Seite, die Anzeige der Flugzeuge und die Bedeutung der AP-Daten sind unter [AirScout-Integration](de-AirScout-Integration) beschrieben.
|
||||
|
||||
---
|
||||
|
||||
@@ -183,6 +335,8 @@ Die drei Audiofunktionen arbeiten unabhängig voneinander:
|
||||
|
||||
CW- und Sprachausgabe können gleichzeitig aktiviert werden. Das ist technisch möglich, im Contest aber nicht zwingend hilfreich. In der Praxis sollte nur die Ausgabe eingeschaltet werden, die im eigenen Stationsbetrieb tatsächlich wahrgenommen werden kann, ohne den Operator dauerhaft zu beschäftigen.
|
||||
|
||||
Die PM-bezogenen Audioausgaben reagieren nur auf Nachrichten, die tatsächlich an das eigene Login-Rufzeichen gerichtet sind. Öffentliche Nachrichten, die durch PM-Catching zusätzlich in der PM-Tabelle erscheinen, und dort eingeblendete Monitoring-Nachrichten bleiben akustisch still.
|
||||
|
||||
### Fallback-Band für relative QRG-Erkennung
|
||||
|
||||
Das Dropdown **Fallback band for relative QRG detection** legt fest, welches Band KST4Contest verwendet, wenn eine relative QRG keinem aktuellen Stationskontext zugeordnet werden kann.
|
||||
@@ -221,11 +375,30 @@ Folgende Einstellungen und Schaltflächen gehören zur lokalen DX-Cluster-Ausgab
|
||||
- **TCP port**: Port, auf dem KST4Contest Verbindungen von DX-Cluster-Clients annimmt. Der Standardwert ist `8000`. Wird der Port während einer laufenden Verbindung geändert, startet KST4Contest den Server auf dem neuen Port neu. Der Logger muss sich anschließend ebenfalls mit dem neuen Port verbinden.
|
||||
- **Fallback band for relative QRG detection**: Das oben beschriebene globale Fallback-Band. Der Testspot verwendet `.300` dieses Bandes. Reale Spots verwenden dagegen die für den jeweiligen Absender erkannte QRG.
|
||||
- **Spotter callsign**: Rufzeichen, das im erzeugten DX-Cluster-Spot als Spotter erscheint. Hier sollte ein anderes Rufzeichen als das im Contest verwendete Stationsrufzeichen eingetragen werden. Einige Logprogramme filtern Spots des eigenen Rufzeichens oder behandeln sie anders als fremde Spots.
|
||||
- **Send test spot**: Sendet einen Testspot für `DL0TEST` auf `.300` des ausgewählten Fallback-Bandes. Der Test funktioniert nur, wenn KST4Contest mit dem Chat verbunden, der lokale DX-Cluster-Server aktiviert und mindestens ein DX-Cluster-Client verbunden ist.
|
||||
- **Send test spot**: Sendet den folgenden Testspot an alle aktuell verbundenen DX-Cluster-Clients:
|
||||
|
||||
KST4Contest erzeugt nicht bei jeder im Chat gefundenen Frequenz automatisch einen Spot. Ein Spot entsteht nur dann, wenn eine gerichtete Nachricht zwischen zwei Stationen auf eine für die eigene Station interessante Antennenrichtung schließen lässt und für den Absender eine nutzbare Frequenz bekannt ist.
|
||||
```text
|
||||
Spotted callsign: DO5AMF
|
||||
Comment: DXC test: You donated $100!
|
||||
Frequency: .300 des ausgewählten Fallback-Bandes
|
||||
```
|
||||
|
||||
Bei einem Fallback-Band von `144 MHz` wird daraus beispielsweise eine Frequenz von ungefähr `144.300 MHz`.
|
||||
|
||||
Alle Spots verwenden eine feste, DXSpider-kompatible 75-Zeichen-Nutzzeile mit einem 30 Zeichen breiten Kommentarfeld. Längere Kommentare werden an dieser Protokollgrenze kontrolliert gekürzt; das DX-Rufzeichen wird dagegen nicht abgeschnitten. Rufzeichen mit mehr als zwölf Zeichen führen dazu, dass der betreffende Spot verworfen und protokolliert wird.
|
||||
|
||||
Der Kommentar des Testspots ist ein bewusst beibehaltenes Easteregg. Er hat keine technische Bedeutung und löst – trotz seiner erfreulich konkreten Formulierung – keine Zahlung aus. Entscheidend ist, dass der Spot im verbundenen Logprogramm erscheint.
|
||||
|
||||
Der Test funktioniert nur, wenn
|
||||
|
||||
1. KST4Contest mit dem ON4KST-Chat verbunden ist,
|
||||
2. der lokale DX-Cluster-Server aktiviert ist und
|
||||
3. mindestens ein DX-Cluster-Client mit KST4Contest verbunden ist.
|
||||
|
||||
KST4Contest erzeugt nicht bei jeder im Chat gefundenen Frequenz automatisch einen Spot. Ein realer Spot entsteht nur dann, wenn eine gerichtete Nachricht zwischen zwei Stationen auf eine für die eigene Station interessante Antennenrichtung schließen lässt und für den Absender eine nutzbare Frequenz bekannt ist.
|
||||
|
||||
Die vollständige Herleitung und die Einrichtung des Logprogramms sind im Kapitel [Integrierter DX-Cluster-Server](de-DX-Cluster-Server) beschrieben.
|
||||
|
||||
### Band-Upgrade-Hinweis nach einem Logeintrag
|
||||
|
||||
Nach einem über UCXLog oder Win-Test empfangenen Logeintrag kann KST4Contest prüfen, ob die gerade gearbeitete Station noch ein weiteres gemeinsames, aber bisher nicht gearbeitetes Band anbietet.
|
||||
@@ -259,17 +432,42 @@ Weitere Hintergründe: [Band-Upgrade-Hinweis nach einem Logeintrag](de-Funktione
|
||||
|
||||
### Sniffer-Einstellungen (ab v1.31)
|
||||
|
||||
Das QSO-Monitoring ist für Stationen gedacht, deren Kommunikation man gezielt verfolgen möchte. Das kann beispielsweise eine seltene Station, eine DXpedition oder eine andere Station des eigenen Contest-Teams sein.
|
||||
Das QSO-Monitoring ist für Stationen gedacht, deren Kommunikation man gezielt verfolgen möchte. Das kann beispielsweise eine seltene Station, eine DXpedition oder eine andere Station des eigenen Contest-Teams sein, deren Sked-Absprachen im allgemeinen Chatverkehr nicht untergehen sollen.
|
||||
|
||||
Für jedes eingetragene Rufzeichen zeigt KST4Contest Nachrichten zusätzlich in der PM-Tabelle an, wenn das Rufzeichen entweder Absender oder Empfänger der Nachricht ist. Die ursprüngliche Nachricht wird dabei nicht aus ihrer normalen Tabelle entfernt.
|
||||
Die Rufzeichen werden im Reiter **Notification** unter **QSO monitoring** verwaltet.
|
||||
|
||||
Überwachte Nachrichten werden in der PM-Tabelle eindeutig gekennzeichnet:
|
||||
Für jedes eingetragene Basisrufzeichen zeigt KST4Contest Nachrichten zusätzlich in der PM-Tabelle an, wenn eine Variante dieses Rufzeichens entweder Absender oder Empfänger der Nachricht ist. Berücksichtigt werden beide verbundenen Chat-Kategorien.
|
||||
|
||||
Die Liste arbeitet bewusst mit dem normalisierten Basisrufzeichen. Folgende Eingaben erzeugen deshalb denselben Monitoring-Eintrag:
|
||||
|
||||
```text
|
||||
Sniffed: (SENDER > RECEIVER) Nachrichtentext
|
||||
DN9APW
|
||||
DN9APW-2
|
||||
DN9APW-70
|
||||
DN9APW-144
|
||||
```
|
||||
|
||||
So wird sichtbar, dass die Nachricht nicht an die eigene Station gerichtet war.
|
||||
In allen Fällen speichert und zeigt KST4Contest den Eintrag als:
|
||||
|
||||
```text
|
||||
DN9APW
|
||||
```
|
||||
|
||||
Damit müssen die verschiedenen KST-Suffixe einer Station nicht einzeln eingetragen werden. Wird später eine Nachricht von `DN9APW-2` gesendet oder an `DN9APW-70` adressiert, wird sie durch denselben Eintrag erfasst.
|
||||
|
||||
Diese Zusammenführung gilt ausschließlich für das QSO-Monitoring. Die aktiven ChatMember-Objekte, vollständigen Nachrichtenempfänger und Chat-Kategorien bleiben getrennt. Eine an `DN9APW-70` gerichtete Nachricht wird deshalb nicht an `DN9APW-2` umgeleitet.
|
||||
|
||||
Überwachte Nachrichten werden in der PM-Tabelle mit den vollständigen sichtbaren Rufzeichen von Absender und Empfänger gekennzeichnet:
|
||||
|
||||
```text
|
||||
Sniffed: (DN9APW-2 > DL0ABC-70) Nachrichtentext
|
||||
```
|
||||
|
||||
Die ursprüngliche Nachricht bleibt gleichzeitig in ihrer normalen Tabelle erhalten. Das Monitoring verändert weder den Nachrichteninhalt noch dessen Routing.
|
||||
|
||||
Erfasst werden Nachrichten, bei denen das überwachte Rufzeichen tatsächlich Absender oder Empfänger ist. Eine bloße Erwähnung des Rufzeichens im Nachrichtentext reicht nicht aus. Öffentliche Nachrichten einer überwachten Station werden ebenfalls angezeigt; als Empfänger erscheint dabei `ALL`.
|
||||
|
||||
Ist eine Nachricht bereits direkt an das eigene Rufzeichen gerichtet, erscheint sie als normale Privatnachricht und erhält keine zusätzliche `Sniffed:`-Kennzeichnung.
|
||||
|
||||
Rufzeichen werden folgendermaßen verwaltet:
|
||||
|
||||
@@ -277,27 +475,97 @@ Rufzeichen werden folgendermaßen verwaltet:
|
||||
2. Ein vorhandenes Rufzeichen per Doppelklick bearbeiten und die Änderung mit `Enter` übernehmen.
|
||||
3. Zum Entfernen den Inhalt einer Tabellenzelle löschen und mit `Enter` bestätigen.
|
||||
|
||||
Doppelte oder syntaktisch ungültige Rufzeichen werden nicht übernommen. Die Liste wird mit **Save Settings** in der `preferences.xml` gespeichert und beim nächsten Programmstart wiederhergestellt.
|
||||
Die Eingabe darf ein sichtbares KST-Suffix oder portable Bestandteile enthalten. KST4Contest normalisiert sie vor dem Speichern auf das Basisrufzeichen. Mehrere Varianten desselben Basisrufzeichens gelten deshalb als Duplikat.
|
||||
|
||||
Die Änderung der Liste wirkt sofort. Zum dauerhaften Speichern anschließend **Save Settings** verwenden. Die Basisrufzeichen werden in der `preferences.xml` gespeichert und beim nächsten Programmstart wiederhergestellt.
|
||||
|
||||
> Die Zusammenführung der KST-Suffixe über das Basisrufzeichen ist ab v1.42 enthalten.
|
||||
|
||||
Weitere Hintergründe und die Abgrenzung zum Nachrichtenrouting: [QSO-Monitoring](de-Funktionen#qso-monitoring-ab-v131).
|
||||
---
|
||||
|
||||
## Shortcut Settings (Schnellzugriff-Schaltflächen)
|
||||
|
||||
Konfiguration von Schnellzugriff-Schaltflächen, die direkt im Hauptfenster erscheinen. Ein Klick auf eine Schaltfläche fügt den konfigurierten Text in das Sendfeld ein. Alle [Variablen](Makros-und-Variablen#variablen) können verwendet werden.
|
||||

|
||||
|
||||
Jeder Eintrag im oberen Bereich des Reiters **Shortcuts** erzeugt eine Schaltfläche oberhalb des Nachrichteneingabefeldes im Hauptfenster. Ein Klick hängt den konfigurierten Text an den bereits vorhandenen Inhalt des Sendfeldes an.
|
||||
|
||||
Enthält der Shortcut eine [Variable](de-Makros-und-Variablen#variablen), wird sie beim Einfügen durch ihren aktuellen Wert ersetzt. Ein Shortcut wie
|
||||
|
||||
```text
|
||||
pse call me at MYQRGSHORT
|
||||
```
|
||||
|
||||
kann dadurch beispielsweise folgenden Text einfügen:
|
||||
|
||||
```text
|
||||
pse call me at 144.388
|
||||
```
|
||||
|
||||
Die exakten Einträge `MYQRG` und `SECONDQRG` werden zusätzlich als QRG-Schaltflächen hervorgehoben. Sie fügen die aktuelle QRG der ersten beziehungsweise zweiten Chat-Kategorie ein.
|
||||
|
||||
Auch der Shortcut `/SETNAME MYQRG` wird hervorgehoben. Beim Anklicken löst KST4Contest `MYQRG` auf und übernimmt den vollständigen Serverbefehl in das Sendfeld. Der Befehl wird nicht automatisch versendet und kann vor dem Senden mit `Enter` oder **TX** noch geprüft werden.
|
||||
|
||||
|
||||
Die Reihenfolge der Tabelle entspricht der Reihenfolge der Schaltflächen im Hauptfenster. Die Einträge werden folgendermaßen verwaltet:
|
||||
|
||||
1. Mit **Add shortcut** wird am Anfang der Liste ein neuer Eintrag angelegt und sofort zur Bearbeitung geöffnet.
|
||||
2. Ein vorhandener Eintrag kann per Doppelklick bearbeitet werden. `Enter` übernimmt die Änderung.
|
||||
3. Wird der Inhalt vollständig gelöscht und anschließend mit `Enter` bestätigt, entfernt KST4Contest den Eintrag.
|
||||
4. Mit **Move selected up** und **Move selected down** wird der markierte Eintrag innerhalb der Liste verschoben.
|
||||
|
||||
Änderungen werden sofort im Hauptfenster sichtbar. Damit sie auch nach dem nächsten Programmstart erhalten bleiben, anschließend **Save Settings** verwenden.
|
||||
|
||||
---
|
||||
|
||||
## Snippet Settings (Text-Snippets)
|
||||
|
||||
Text-Snippets sind über folgende Wege abrufbar:
|
||||
Snippets sind längere Textbausteine, die vor allem für Nachrichten an eine ausgewählte Station vorgesehen sind. Sie können über folgende Wege aufgerufen werden:
|
||||
|
||||
- **Rechtsklick** auf ein Rufzeichen in der Benutzerliste
|
||||
- **Rechtsklick** in der CQ-Nachrichtentabelle
|
||||
- **Rechtsklick** in der PM-Nachrichtentabelle
|
||||
- **Tastenkombinationen**: `Ctrl+1` bis `Ctrl+0` für die ersten 10 Snippets
|
||||
- per Rechtsklick auf eine Station in der Benutzerliste,
|
||||
- per Rechtsklick auf eine Nachricht in der öffentlichen Chat-Tabelle,
|
||||
- per Rechtsklick auf eine Nachricht in der PM-Tabelle oder
|
||||
- mit `Ctrl+1` bis `Ctrl+0` für die ersten zehn Einträge der Snippet-Liste.
|
||||
|
||||
Wenn in der Benutzerliste ein Rufzeichen ausgewählt ist, wird der Snippet als Direktnachricht adressiert:
|
||||
`/CQ RUFZEICHEN <Snippet-Text>`
|
||||
Bei den Tastenkombinationen entspricht die Zuordnung der Tabellenreihenfolge:
|
||||
|
||||
| Tastenkombination | Snippet |
|
||||
|---|---:|
|
||||
| `Ctrl+1` | erster Eintrag |
|
||||
| `Ctrl+2` | zweiter Eintrag |
|
||||
| … | … |
|
||||
| `Ctrl+9` | neunter Eintrag |
|
||||
| `Ctrl+0` | zehnter Eintrag |
|
||||
|
||||
Ein über das Kontextmenü ausgewähltes Snippet wird an den bereits vorbereiteten Nachrichtentext angehängt. Die Auswahl einer Station oder Nachricht hat das Sendfeld zuvor normalerweise bereits mit dem passenden `/cq`-Empfänger vorbereitet.
|
||||
|
||||
Eine Tastenkombination verhält sich etwas anders: Sie ersetzt den bisherigen Inhalt des Sendfeldes durch eine vollständig adressierte Privatnachricht:
|
||||
|
||||
```text
|
||||
/cq RUFZEICHEN Snippet-Text
|
||||
```
|
||||
|
||||
Dabei wird das vollständige sichtbare Rufzeichen einschließlich eines vorhandenen Suffixes verwendet. Für `9A0BB-70` entsteht daher beispielsweise:
|
||||
|
||||
```text
|
||||
/cq 9A0BB-70 pse ur qrg?
|
||||
```
|
||||
|
||||
Die Chat-Kategorie der ausgewählten Station bleibt für den späteren Versand erhalten. Ist keine Station ausgewählt oder ist für die gedrückte Tastenkombination kein Snippet vorhanden, wird nichts eingefügt.
|
||||
|
||||
Variablen werden beim Einfügen des Snippets aufgelöst. Stationsbezogene Variablen wie `QRZNAME`, `FIRSTAP` oder `SECONDAP` verwenden die aktuell ausgewählte Station. Der vorbereitete Text wird nicht automatisch gesendet und kann deshalb noch geprüft oder geändert werden. `Enter` oder **TX** sendet die Nachricht; `Esc` leert das Sendfeld.
|
||||
|
||||
Die Snippet-Liste wird genauso bearbeitet wie die Shortcut-Liste:
|
||||
|
||||
1. **Add new snippet** legt am Anfang der Liste einen neuen Eintrag an.
|
||||
2. Ein Doppelklick öffnet einen vorhandenen Eintrag zur Bearbeitung.
|
||||
3. `Enter` übernimmt die Änderung.
|
||||
4. Ein leer bestätigter Eintrag wird entfernt.
|
||||
5. **Move selected up** und **Move selected down** ändern die Reihenfolge und damit auch die Zuordnung zu `Ctrl+1` bis `Ctrl+0`.
|
||||
|
||||
Die Kontextmenüs und Tastenkombinationen werden nach einer Änderung sofort aktualisiert. Für die dauerhafte Speicherung anschließend **Save Settings** verwenden.
|
||||
|
||||
Eine vollständige Übersicht der verfügbaren Platzhalter und ihrer Grenzen steht unter [Makros und Variablen](de-Makros-und-Variablen).
|
||||
|
||||
---
|
||||
|
||||
@@ -305,7 +573,7 @@ Wenn in der Benutzerliste ein Rufzeichen ausgewählt ist, wird der Snippet als D
|
||||
|
||||

|
||||
|
||||
Ein Beacon sendet in regelmäßigen Abständen eine öffentliche CQ-Nachricht. Er ist für Betriebssituationen gedacht, in denen die eigene Station über längere Zeit auf einer festen Frequenz ruft. Andere Stationen erhalten dadurch eine aktuelle QRG-Information, ohne dass der Operator denselben Text wiederholt von Hand in den Chat schreiben muss.
|
||||
Ein Beacon sendet in regelmäßigen Abständen eine öffentliche CQ-Nachricht. Er ist für Betriebssituationen gedacht, in denen die eigene Station über längere Zeit auf einer festen Frequenz ruft. Andere Stationen erhalten dadurch eine aktuelle QRG-Information, ohne dass der Operator denselben Text immer wieder von Hand in den Chat schreiben muss.
|
||||
|
||||
KST4Contest verwendet einen gemeinsamen Timer für beide Chat-Kategorien. Aktivierung und Nachrichtentext werden trotzdem getrennt konfiguriert:
|
||||
|
||||
@@ -313,27 +581,62 @@ KST4Contest verwendet einen gemeinsamen Timer für beide Chat-Kategorien. Aktivi
|
||||
- **Beacon message** enthält den öffentlichen Nachrichtentext dieser Kategorie.
|
||||
- **Shared beacon interval** legt das gemeinsame Intervall für beide Kategorien fest.
|
||||
|
||||
Sind beide Beacons aktiviert, werden sie beim selben Timer-Lauf nacheinander in ihren jeweiligen Kategorien gesendet. Der zweite Beacon wird nur berücksichtigt, wenn auch der zweite Chat aktiviert und verbunden ist.
|
||||
Sind beide Beacons aktiviert, werden sie beim selben Timer-Lauf nacheinander in ihren jeweiligen Kategorien gesendet. Der zweite Beacon wird nur berücksichtigt, wenn die zweite Kategorie in derselben ON4KST-Sitzung aktiviert und vollständig synchronisiert ist.
|
||||
|
||||
### Intervall und Timer-Verhalten
|
||||
|
||||
Das Intervall wird in ganzen Minuten angegeben. Der kleinste zulässige Wert ist eine Minute.
|
||||
|
||||
Nach dem Aufbau der Chat-Verbindung prüft KST4Contest die Beacons erstmals nach ungefähr zehn Sekunden. Anschließend gilt das eingestellte Intervall. Wird der Wert während einer laufenden Verbindung geändert, beginnt der Countdown mit dem neuen Intervall erneut. Die Änderung selbst löst keine sofortige Nachricht aus.
|
||||
Nach dem Aufbau der Chat-Verbindung prüft KST4Contest die Beacons erstmals nach ungefähr zehn Sekunden. Anschließend gilt das eingestellte Intervall.
|
||||
|
||||
Wird das Intervall während einer laufenden Verbindung geändert, beginnt der Countdown mit dem neuen Wert erneut. Die Änderung selbst löst keine sofortige Beacon-Nachricht aus.
|
||||
|
||||
Beide Kategorien verwenden denselben Timer. Unterschiedliche Intervalle für den ersten und zweiten Chat können deshalb nicht eingestellt werden.
|
||||
|
||||
### Nachrichtentext und Variablen
|
||||
|
||||
Ein Beacon darf nach der Variablenauflösung höchstens 120 Zeichen enthalten. KST4Contest prüft deshalb nicht nur das eingetragene Template, sondern den tatsächlich zu sendenden Text.
|
||||
Ein Beacon darf die [globalen Variablen](de-Makros-und-Variablen#variablen-im-beacon) verwenden, die sich ausschließlich auf die eigene Station beziehen:
|
||||
|
||||
Im Beacon können alle [globalen Variablen](de-Makros-und-Variablen#variablen-im-beacon) verwendet werden, beispielsweise:
|
||||
- `MYQRG`
|
||||
- `MYQRGSHORT`
|
||||
- `SECONDQRG`
|
||||
- `MYLOCATOR`
|
||||
- `MYLOCATORSHORT`
|
||||
- `MYCALL`
|
||||
- `MYQTF`
|
||||
|
||||
Eine mögliche Nachricht für die erste Chat-Kategorie ist:
|
||||
|
||||
```text
|
||||
calling cq at MYQRG, ant MYQTF deg, loc MYLOCATOR
|
||||
calling cq at MYQRGSHORT, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Die Variablen werden bei jedem Timer-Lauf neu aufgelöst. Ändert die Logsoftware zwischenzeitlich die in `MYQRG` gespeicherte Frequenz, verwendet bereits der nächste Beacon den neuen Wert.
|
||||
Für den zweiten Chat muss `SECONDQRG` verwendet werden, wenn dessen Frequenz von der ersten Kategorie abweicht:
|
||||
|
||||
Stationsbezogene Variablen wie `QRZNAME`, `FIRSTAP` oder `SECONDAP` benötigen dagegen eine ausgewählte Gegenstation. Da ein öffentlicher Beacon keine Gegenstation adressiert, werden diese Variablen im Beacon nicht aufgelöst.
|
||||
```text
|
||||
calling cq at SECONDQRG, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Die Variablen werden bei jedem Timer-Lauf neu aufgelöst. Ändert die Logsoftware zwischenzeitlich die in `MYQRG` gespeicherte Frequenz, verwendet bereits der nächste Beacon den aktualisierten Wert. Das Template selbst muss dafür nicht geändert werden.
|
||||
|
||||
`MYQRG` und `MYQRGSHORT` beziehen sich immer auf die erste Chat-Kategorie. Die Auswahl oder Aktivierung des zweiten Chats ändert diese Zuordnung nicht.
|
||||
|
||||
Stationsbezogene Variablen wie `QRZNAME`, `FIRSTAP` oder `SECONDAP` benötigen eine ausgewählte Gegenstation. Da ein öffentlicher Beacon keine bestimmte Station adressiert, werden diese Variablen im Beacon nicht aufgelöst.
|
||||
|
||||
### Prüfung des Nachrichtentextes
|
||||
|
||||
KST4Contest prüft sowohl das eingetragene Template als auch den nach der Variablenauflösung tatsächlich zu sendenden Text.
|
||||
|
||||
Für Beacon-Nachrichten gelten folgende Bedingungen:
|
||||
|
||||
- Der endgültige Nachrichtentext darf nicht leer sein.
|
||||
- Er darf höchstens 120 Zeichen enthalten.
|
||||
- Das Protokoll-Trennzeichen `|` ist nicht zulässig.
|
||||
- Zeilenumbrüche sind nicht zulässig.
|
||||
|
||||
Eine ungültige Eingabe wird nicht als neue Beacon-Konfiguration übernommen. Wird ein Template erst durch eine spätere Variablenauflösung ungültig, beispielsweise weil der aufgelöste Text länger als 120 Zeichen ist, wird dieser Beacon-Lauf ausgelassen.
|
||||
|
||||
Ein Template, das ausschließlich aus einer momentan noch leeren globalen Variable besteht, kann gespeichert werden. Das ist beispielsweise beim Start möglich, bevor die erste QRG vom Logprogramm empfangen wurde. Solange die Variable keinen verwendbaren Inhalt liefert, sendet KST4Contest jedoch keine leere Nachricht.
|
||||
|
||||
### Wann sollte der Beacon ausgeschaltet werden?
|
||||
|
||||
@@ -342,6 +645,7 @@ Der Beacon ist nur dann hilfreich, wenn seine QRG-Angabe zum tatsächlichen Betr
|
||||
Im Klartext: Solange auf einer festen QRG CQ gerufen wird, spart der Beacon Arbeit. Beim „Schleichen“ über das Band sollte er ausgeschaltet werden.
|
||||
|
||||
Änderungen wirken während der laufenden Verbindung. Damit Aktivierung, Texte und Intervall auch nach dem nächsten Programmstart erhalten bleiben, anschließend **Save Settings** verwenden.
|
||||
|
||||
---
|
||||
|
||||
## Messagehandling Settings (ab v1.25)
|
||||
@@ -362,9 +666,19 @@ Eine zweckmäßige Nachricht ist beispielsweise:
|
||||
Sri, I am not taking part in this contest. No skeds.
|
||||
```
|
||||
|
||||
Das Präfix `[KST4C Automsg]` muss nicht in das Eingabefeld geschrieben werden. KST4Contest ergänzt es automatisch.
|
||||
|
||||
Die beim Empfänger sichtbare Nachricht lautet daher beispielsweise:
|
||||
|
||||
```text
|
||||
[KST4C Automsg] Sri, I am not taking part in this contest. No skeds.
|
||||
```
|
||||
|
||||
Die eingegangene Privatnachricht bleibt sichtbar. Die Funktion blockiert oder verwirft keine Anfrage, sondern erspart lediglich die wiederholte manuelle Antwort.
|
||||
|
||||
Die Antwort wird in derselben Chat-Kategorie gesendet, in der die Privatnachricht eingegangen ist. Das ist bei einem parallelen Login in zwei Kategorien entscheidend: Eine Nachricht aus dem Microwave-Chat darf nicht versehentlich im VHF/UHF-Chat beantwortet werden.
|
||||
Die Antwort wird an das vollständige Rufzeichen des Absenders einschließlich eines vorhandenen Suffixes und in derselben Chat-Kategorie gesendet, in der die Privatnachricht eingegangen ist. Das ist bei einem parallelen Login in zwei Kategorien entscheidend: Eine Nachricht aus dem Microwave-Chat darf nicht versehentlich im VHF/UHF-Chat beantwortet werden.
|
||||
|
||||
Ein leerer oder ausschließlich aus Leerzeichen bestehender Antworttext erzeugt keine automatische Nachricht. Enthält der Text ein Protokoll-Trennzeichen wie `|` oder einen Zeilenumbruch, wird die Antwort ebenfalls verworfen.
|
||||
|
||||
### Automatische QRG-Antwort
|
||||
|
||||
@@ -378,16 +692,24 @@ freq?
|
||||
pse qrg
|
||||
```
|
||||
|
||||
Die Antwort enthält nur die QRG der Kategorie, in der die Anfrage eingegangen ist:
|
||||
Die Antwort enthält ausschließlich die QRG der Kategorie, in der die Anfrage eingegangen ist:
|
||||
|
||||
| Eingegangene Privatnachricht | Verwendete QRG |
|
||||
|---|---|
|
||||
| Hauptkategorie | aktuelle QRG der Hauptkategorie |
|
||||
| zweite Chat-Kategorie | aktuelle QRG der zweiten Kategorie |
|
||||
|
||||
Eine mögliche Antwort lautet:
|
||||
|
||||
```text
|
||||
[KST4C Automsg] QRG is: 144.300.00
|
||||
```
|
||||
|
||||
Die Werte stammen aus denselben QRG-Feldern, die auch von `MYQRG` und `SECONDQRG` verwendet werden. Die Haupt-QRG kann manuell eingetragen oder durch die [TRX-Synchronisation](#trx-sync-einstellungen) aktualisiert werden. Für die zweite Kategorie wird der dort konfigurierte beziehungsweise manuell eingetragene Wert verwendet.
|
||||
|
||||
Sind die allgemeine und die QRG-bezogene Antwort gleichzeitig aktiviert, hat die QRG-Antwort Vorrang. Eine erkannte QRG-Anfrage erzeugt daher nicht zusätzlich den allgemeinen Antworttext.
|
||||
Ist für die betreffende Kategorie keine QRG vorhanden, sendet KST4Contest keine unvollständige Antwort. Eine Nachricht wie `QRG is:` ohne Frequenz würde zwar auf die Frage reagieren, dem Anfragenden aber keine Information liefern. Sie wird deshalb bereits vor der Übergabe an die Sendequeue verworfen.
|
||||
|
||||
Sind die allgemeine und die QRG-bezogene Antwort gleichzeitig aktiviert, hat die QRG-Antwort Vorrang. Eine erkannte QRG-Anfrage erzeugt daher nicht zusätzlich den allgemeinen Antworttext. Fehlt die benötigte QRG, fällt KST4Contest auch nicht auf die allgemeine Antwort zurück.
|
||||
|
||||
### Schutz vor wiederholten Antworten
|
||||
|
||||
@@ -399,9 +721,16 @@ Jede automatisch erzeugte Nachricht trägt das feste Präfix:
|
||||
|
||||
Die allgemeine und die QRG-bezogene Antwort reagieren nicht auf Nachrichten, die dieses Präfix bereits enthalten. Dadurch beantworten sich zwei entsprechend arbeitende Clients nicht gegenseitig in einer Schleife.
|
||||
|
||||
Zusätzlich gilt eine gemeinsame Sperrzeit von zwei Minuten für beide Antwortarten. Die Sperre wird getrennt je Rufzeichen und Chat-Kategorie geführt. Hat eine Station gerade in der Hauptkategorie eine automatische Antwort erhalten, kann sie deshalb weiterhin eine Antwort in der zweiten Kategorie erhalten. Weitere Nachrichten derselben Station in derselben Kategorie lösen während der folgenden zwei Minuten dagegen keine neue automatische Antwort aus.
|
||||
Zusätzlich gilt eine gemeinsame Sperrzeit von zwei Minuten für beide Antwortarten. Die Sperre wird getrennt nach vollständigem Rufzeichen und Chat-Kategorie geführt.
|
||||
|
||||
Die Sperrzeit beginnt nur, wenn KST4Contest tatsächlich eine Antwort sendet.
|
||||
Daraus folgt:
|
||||
|
||||
- `CALLSIGN-2` und `CALLSIGN-70` besitzen getrennte Sperrzeiten.
|
||||
- Dasselbe vollständige Rufzeichen kann in einer anderen Chat-Kategorie unabhängig beantwortet werden.
|
||||
- Eine allgemeine Antwort sperrt für zwei Minuten auch eine QRG-Antwort an dasselbe Rufzeichen in derselben Kategorie.
|
||||
- Eine QRG-Antwort sperrt entsprechend auch die allgemeine Antwort.
|
||||
|
||||
Die Sperrzeit beginnt nur, wenn KST4Contest eine vollständige und lokal gültige Antwort in die Sendequeue übernimmt. Eine fehlende QRG, ein leerer allgemeiner Antworttext oder ein wegen ungültiger Zeichen verworfener Text startet keine Sperrzeit. Sobald die fehlende Information korrigiert wurde, kann daher unmittelbar eine gültige Antwort erzeugt werden.
|
||||
|
||||
> **Hinweis**: Der Antworttext sollte den tatsächlichen Status eindeutig benennen. Wer den Contest nur beobachtet und keine Skeds fahren möchte, sollte genau das mitteilen. Eine vage Nachricht erzeugt im Zweifel nur die nächste Rückfrage – und damit exakt die Arbeit, welche die Funktion vermeiden soll.
|
||||
|
||||
@@ -453,16 +782,63 @@ Nach Änderungen **Save Settings** verwenden, damit Port, Stationsname, Broadcas
|
||||
Datenbehandlung und QRG-Auswahl: [Log-Synchronisation – Win-Test](de-Log-Synchronisation#win-test)
|
||||
|
||||
|
||||
## PSTRotator-Einstellungen (ab v1.31)
|
||||
## PSTRotator-Einstellungen (ab v1.31, vollständig konfigurierbar ab v1.40)
|
||||
|
||||
KST4Contest kann die Antennenrichtung über PSTRotator steuern.
|
||||
KST4Contest kann eine ausgewählte Antennenrichtung über die UDP-Schnittstelle von [PSTRotator](https://www.pstrotator.com/) einstellen und die von PSTRotator gemeldete aktuelle Position als eigene QTF übernehmen.
|
||||
|
||||
Einstellungen:
|
||||
- **Aktivieren/Deaktivieren**: Checkbox in den Preferences (ab v1.40).
|
||||
- **IP-Adresse**: IP-Adresse des PSTRotator-Rechners (Standard: `127.0.0.1` bei Betrieb auf demselben PC).
|
||||
- **Port**: Kommunikationsport von PSTRotator.
|
||||
Die Einstellungen befinden sich im Reiter **Station**:
|
||||
|
||||
> **Hinweis**: Nach einem Klick auf den Richtungs-Button wartet KST4Contest kurz auf die Rotatorantwort. Bei langsamen Rotoren (z. B. SPID) kann es zu einer kleinen Verzögerung kommen.
|
||||
| Einstellung | Standardwert | Verwendung |
|
||||
|---|---:|---|
|
||||
| **Enable PSTRotator** | deaktiviert | Startet die UDP-Kommunikation mit PSTRotator |
|
||||
| **PSTRotator host** | `127.0.0.1` | Hostname oder IP-Adresse des Rechners, auf dem PSTRotator läuft |
|
||||
| **PSTRotator UDP port** | `12000` | UDP-Port, auf dem PSTRotator die Steuerbefehle empfängt |
|
||||
|
||||
Bei Betrieb auf demselben Rechner ist `127.0.0.1` normalerweise die eindeutigste Einstellung. Läuft PSTRotator auf einem anderen Rechner im Stationsnetz, muss dessen erreichbare IP-Adresse oder DNS-Name eingetragen werden.
|
||||
|
||||
Der Port darf zwischen `1` und `65534` liegen. Port `65535` ist nicht möglich, weil PSTRotator seine Positionsmeldungen auf dem jeweils folgenden Port sendet.
|
||||
|
||||
### PSTRotator vorbereiten
|
||||
|
||||
In PSTRotator muss unter **Communication → UDP Control Port** derselbe UDP-Port eingetragen werden wie in KST4Contest. Anschließend muss **UDP Control** in PSTRotator aktiviert werden.
|
||||
|
||||
Bei der Standardeinstellung ergibt sich folgendes Portpaar:
|
||||
|
||||
| Richtung | UDP-Port |
|
||||
|---|---:|
|
||||
| KST4Contest → PSTRotator | `12000` |
|
||||
| PSTRotator → KST4Contest | `12001` |
|
||||
|
||||
KST4Contest bindet den Empfangsport automatisch. Er wird nicht separat konfiguriert.
|
||||
|
||||
Bei Betrieb auf zwei Rechnern müssen die lokale Firewall und das Stationsnetz UDP-Pakete in beiden Richtungen zulassen. Ist der Empfangsport bereits durch ein anderes Programm belegt, kann KST4Contest die Positionsmeldungen nicht empfangen.
|
||||
|
||||
Das vollständige UDP-Protokoll ist im [PSTRotatorAz User Manual](https://www.qsl.net/yo3dmu/ANT/PstRotatorAz%20User%20Manual.pdf) beschrieben.
|
||||
|
||||
### Übernahme der aktuellen QTF
|
||||
|
||||
KST4Contest fragt PSTRotator alle zwei Sekunden nach der aktuellen Azimutposition und dem Betriebsmodus. Die zurückgemeldete Azimutposition wird als `actualQTF` übernommen.
|
||||
|
||||
Bei aktivierter PSTRotator-Integration ist das QTF-Feld im Hauptfenster deshalb nicht manuell editierbar. Es zeigt die zuletzt von PSTRotator gemeldete Position.
|
||||
|
||||
Diese QTF wird unter anderem verwendet für:
|
||||
|
||||
- den Richtungsfilter,
|
||||
- die Bewertung von Richtungsgelegenheiten,
|
||||
- den Priority Score,
|
||||
- die Darstellung des Antennensektors auf der Stationskarte,
|
||||
- die AP- und Sked-Timeline und
|
||||
- die Variable `MYQTF`.
|
||||
|
||||
Eine empfangene Rotatorposition ist damit nicht nur eine Anzeige. Sie verändert mehrere Funktionen, die auf der aktuellen Antennenrichtung beruhen.
|
||||
|
||||
KST4Contest verwendet derzeit nur den Azimut. Eine Elevationssteuerung oder eine vollständige Azimut-/Elevationsnachführung ist nicht Bestandteil dieser Integration.
|
||||
|
||||
### Änderungen übernehmen
|
||||
|
||||
Aktivierung, Host und Port werden beim Start der Rotatorverbindung ausgewertet. Nach einer Änderung sollte die ON4KST-Verbindung getrennt und erneut aufgebaut oder KST4Contest neu gestartet werden.
|
||||
|
||||
Anschließend **Save Settings** verwenden, damit die Werte auch beim nächsten Programmstart wiederhergestellt werden.
|
||||
|
||||
---
|
||||
|
||||
@@ -488,6 +864,8 @@ Die interne SQLite-Datenbank speichert die contestbezogenen Zustände unabhängi
|
||||
- manuell gesetzte NOT-QRV-Tags pro Band und
|
||||
- gearbeitete vierstellige Großfelder pro Band.
|
||||
|
||||
Eine Ausnahme bilden Worked-Markierungen des Simplelogfile-Interpreters. Sie werden nur aus der ausgewählten Datei in den laufenden Zustand übernommen und nicht in SQLite persistiert. Die Datei ist die dauerhafte Quelle und besitzt keinen automatischen Contest-Reset. Ein Datenbank-Reset verändert oder leert sie nicht; enthaltene Rufzeichen werden bei der nächsten periodischen Auswertung erneut als gearbeitet markiert.
|
||||
|
||||
Als Schlüssel wird das normalisierte Rufzeichen ohne sichtbare Chat-Klammern oder Kategorieformatierung verwendet. Dadurch können aktive Varianten desselben Rufzeichens konsistent ausgewertet werden.
|
||||
|
||||
Worked- und NOT-QRV-Informationen laufen drei Tage nach ihrer letzten Änderung automatisch ab. Gespeicherte Großfelder laufen drei Tage nach dem zugehörigen Logeintrag ab. Ein manuelles Zurücksetzen vor jedem Contest ist deshalb normalerweise nicht erforderlich.
|
||||
@@ -502,7 +880,7 @@ Anzeige und Herleitung: [Gearbeitete Rufzeichen, neue Bänder und neue Großfeld
|
||||
|
||||
## Dark Mode (ab v1.26)
|
||||
|
||||
Umschaltbar über das Menü: **Window → Use Dark Mode**. Die Farben können über CSS individuell angepasst werden.
|
||||
Der Dark Mode wird über **Windows → Use dark mode design** aktiviert. Mit **Windows → Use default mode design** wird wieder das normale helle Farbschema geladen.
|
||||
|
||||
---
|
||||
|
||||
@@ -510,6 +888,6 @@ Umschaltbar über das Menü: **Window → Use Dark Mode**. Die Farben können ü
|
||||
|
||||
Nach **jeder** Änderung **„Save Settings"** klicken! Ohne Speichern gehen alle Änderungen beim nächsten Start verloren.
|
||||
|
||||
- Speicherort: unter Linux `~/.praktikst/preferences.xml` und unter Windows `%USERPROFILE%\.praktikst\preferences.xml` (bzw. `C:\Users\<Benutzername>\.praktikst\preferences.xml`)
|
||||
- Speicherort: unter Linux und macOS `~/.praktiKST/preferences.xml` und unter Windows `%USERPROFILE%\.praktiKST\preferences.xml` (bzw. `C:\Users\<Benutzername>\.praktiKST\preferences.xml`)
|
||||
- Ab v1.21: Fenstergrößen und Divider-Positionen werden ebenfalls gespeichert.
|
||||
- Bei Problemen: Konfigurationsdatei löschen → KST4Contest erstellt eine neue mit Standardwerten.
|
||||
|
||||
@@ -10,13 +10,15 @@ KST4Contest übernimmt gearbeitete Stationen aus dem Logprogramm und stellt dara
|
||||
|
||||
## Methode 1: Universal File Based Callsign Interpreter (Simplelogfile)
|
||||
|
||||
KST4Contest liest eine Logdatei und sucht mit einem konfigurierbaren regulären Ausdruck nach Rufzeichen. Die Datei wird ausschließlich gelesen und nicht verändert. Auch binäre Logdateien können verwendet werden; nicht als Text interpretierbare Inhalte werden übersprungen.
|
||||
KST4Contest liest die ausgewählte Textdatei einmal pro Minute und sucht darin mit einem fest eingebauten regulären Ausdruck nach Rufzeichen. Jedes gefundene Rufzeichen wird auf sein Basisrufzeichen normalisiert. Der globale Worked-Status gilt dadurch für alle derzeit aktiven Chat-Varianten dieses Rufzeichens.
|
||||
|
||||
Der Vorteil liegt in der breiten Kompatibilität: Die Funktion benötigt keine besondere Netzwerkschnittstelle des Logprogramms.
|
||||
|
||||
Die Grenze ist ebenso eindeutig: Aus einem reinen Rufzeichentreffer lassen sich weder Band noch Locator zuverlässig ableiten. Der Simplelogfile-Interpreter kann deshalb nur den globalen Worked-Status setzen. Er erzeugt keine bandbezogene `X`-Markierung, kein Worked-Großfeld und keine belastbare Grundlage für den Band-Upgrade-Hinweis nach einem Logeintrag.
|
||||
|
||||
Den Pfad der Logdatei und den regulären Ausdruck im Reiter **Log sync** eintragen. Für bandbezogene Auswertungen sollte nach Möglichkeit eine der Netzwerkschnittstellen verwendet werden.
|
||||
Den Pfad der Textdatei im Reiter **Log sync** auswählen. Fehlt die Datei, legt KST4Contest sie an und zeigt einmalig einen nicht blockierenden Hinweis mit dem Pfad und den nächsten Prüfschritten an. Lese- oder Erstellungsfehler werden protokolliert; die minütliche Auswertung läuft beim nächsten Termin weiter. Für bandbezogene Auswertungen sollte nach Möglichkeit eine der Netzwerkschnittstellen verwendet werden.
|
||||
|
||||
Der aus dem Simplelogfile abgeleitete Worked-Status wird nicht in der internen SQLite-Datenbank gespeichert. Die ausgewählte Datei ist die dauerhafte Quelle und wird auch nach einem Neustart erneut eingelesen. Der Interpreter setzt nur positive Worked-Markierungen; er entfernt während der laufenden Programmsitzung keine bereits gesetzte Markierung und führt beim Wechsel zu einem neuen Contest keinen automatischen Reset durch. Auch ein manueller Datenbank-Reset ändert oder leert das Simplelogfile nicht. Darin enthaltene Rufzeichen werden bei der nächsten Auswertung innerhalb einer Minute erneut als gearbeitet markiert. Vor jedem Contest sollte deshalb geprüft werden, ob das Logprogramm genau die aktuelle Contestdatei beschreibt.
|
||||
|
||||
---
|
||||
|
||||
@@ -163,7 +165,7 @@ Neben der QSO-Synchronisation übertragen UCXLog und andere Programme auch die *
|
||||
|
||||

|
||||
|
||||
**Ergebnis**: Die eigene QRG muss im Chat nie mehr manuell eingegeben werden – ein Klick auf den MYQRG-Button oder die Verwendung der Variable im Beacon genügt.
|
||||
**Ergebnis**: Eine aktivierte Schnittstelle aktualisiert `MYQRG`, sobald sie tatsächlich gültige Frequenzpakete liefert. Das Aktivieren allein erzeugt noch keine QRG. Kommen keine passenden Pakete an, muss die Schnittstelle geprüft oder die QRG nach dem Deaktivieren beider automatischen Quellen manuell gepflegt werden.
|
||||
|
||||
**Quellen für die eigene QRG (MYQRG):**
|
||||
- UCXLog, N1MM+, DXLog.net, QARTest via UDP-Port 12060
|
||||
@@ -191,7 +193,7 @@ Für DM5M-typische Setups (2 Radios, 2 Computer, eine KST4Contest-Instanz oder z
|
||||
|
||||
## Interne Datenbank
|
||||
|
||||
KST4Contest speichert Worked-, NOT-QRV- und Großfeldinformationen in einer eigenen SQLite-Datenbank. Sie ist von der Datenbank des Logprogramms unabhängig.
|
||||
KST4Contest speichert Worked-, NOT-QRV- und Großfeldinformationen aus den Netzwerkschnittstellen sowie manuelle Markierungen in einer eigenen SQLite-Datenbank. Sie ist von der Datenbank des Logprogramms unabhängig. Simplelogfile-Treffer sind davon ausgenommen und werden bei jeder Programmsitzung aus der ausgewählten Datei neu abgeleitet.
|
||||
|
||||
Die Datenquellen liefern unterschiedlich genaue Informationen:
|
||||
|
||||
@@ -201,6 +203,6 @@ Die Datenquellen liefern unterschiedlich genaue Informationen:
|
||||
| QSO-UDP-Listener | ja | ja, wenn im Paket enthalten | ja, wenn Band und Locator enthalten sind |
|
||||
| Win-Test-Netzwerk-Listener | ja | ja | ja, wenn ein Locator enthalten ist |
|
||||
|
||||
Die Daten werden beim Programmstart wieder geladen und bei neuen Logeinträgen während des Betriebs aktualisiert. Sie laufen nach drei Tagen automatisch ab. Ein Reset vor jedem Contest ist daher normalerweise nicht erforderlich.
|
||||
Die in SQLite gespeicherten Daten werden beim Programmstart wieder geladen und bei neuen Logeinträgen während des Betriebs aktualisiert. Sie laufen nach drei Tagen automatisch ab. Ein Reset vor jedem Contest ist daher normalerweise nicht erforderlich. Für den Simplelogfile-Interpreter gilt diese Lebensdauer nicht: Seine Datei bleibt die dauerhafte Quelle und wird durch einen Datenbank-Reset weder geändert noch geleert. Enthaltene Rufzeichen setzt die nächste periodische Auswertung erneut auf den globalen Worked-Status.
|
||||
|
||||
Ein vollständiger manueller Reset entfernt Worked-Markierungen, NOT-QRV-Tags und Worked-Großfelder gemeinsam. Weitere Einzelheiten: [Worked Station Database Settings](de-Konfiguration#worked-station-database-settings-gearbeitete-stationen-datenbank).
|
||||
Ein vollständiger manueller Reset entfernt Worked-Markierungen, NOT-QRV-Tags und Worked-Großfelder gemeinsam. Weitere Einzelheiten: [Worked Station Database Settings](de-Konfiguration#worked-station-database-settings-gearbeitete-stationen-datenbank).
|
||||
|
||||
@@ -2,177 +2,369 @@
|
||||
|
||||
> 🇬🇧 [English version](en-Macros-and-Variables) | 🇩🇪 Du liest gerade die deutsche Version
|
||||
|
||||
KST4Contest bietet ein flexibles System aus Text-Snippets, Shortcuts und eingebauten Variablen, die den Chat-Workflow im Contest erheblich beschleunigen.
|
||||
KST4Contest unterscheidet zwischen Shortcut-Schaltflächen, Text-Snippets und Variablen. Shortcuts und Snippets enthalten vorbereitete Texte. Variablen ergänzen Informationen, die sich während des Betriebs ändern können.
|
||||
|
||||
Der eingefügte Text bleibt im Sendfeld sichtbar und kann vor dem Senden geprüft oder geändert werden.
|
||||
|
||||
---
|
||||
|
||||
## Überblick
|
||||
|
||||
| Typ | Aufruf | Zweck |
|
||||
| Mechanismus | Aufruf | Verwendung |
|
||||
|---|---|---|
|
||||
| **Shortcuts** | Button in der Toolbar | Schneller Text-Insert ins Sendfeld |
|
||||
| **Snippets** | Rechtsklick / Ctrl+1..0 | Text-Bausteine, optionaler PM-Versand |
|
||||
| **Variablen** | In allen Text-Feldern verwendbar | Dynamische Werte (QRG, Locator, AP-Daten) |
|
||||
| **Shortcut** | Schaltfläche oberhalb des Sendfeldes | Fügt einen konfigurierten Text in das Sendfeld ein |
|
||||
| **Snippet** | Kontextmenü oder `Ctrl+1` bis `Ctrl+0` | Bereitet einen Text für die ausgewählte Station vor |
|
||||
| **Variable** | Platzhalter innerhalb eines Nachrichtentextes | Fügt aktuelle QRG-, Locator-, Richtungs-, Stations- oder AirScout-Informationen ein |
|
||||
|
||||
Shortcuts und Snippets speichern Texte. Variablen liefern die dazugehörigen aktuellen Werte.
|
||||
|
||||
Ein Shortcut wie
|
||||
|
||||
```text
|
||||
pse sked?
|
||||
```
|
||||
|
||||
fügt immer denselben Text ein. Ein Shortcut mit
|
||||
|
||||
```text
|
||||
pse call me at MYQRGSHORT
|
||||
```
|
||||
|
||||
verwendet dagegen die QRG, die beim Anklicken des Shortcuts aktuell in KST4Contest hinterlegt ist.
|
||||
|
||||
---
|
||||
|
||||
## Shortcuts (Schnellzugriff-Schaltflächen)
|
||||
## Shortcut-Schaltflächen
|
||||
|
||||
Konfigurierbar in den Preferences → **Shortcut Settings**.
|
||||
Shortcuts werden unter **Preferences → Shortcut Settings** konfiguriert.
|
||||
|
||||
- Jeder konfigurierte Text erzeugt **einen Button** in der Benutzeroberfläche.
|
||||
- Ein Klick fügt den Text in das **Sendfeld** ein.
|
||||
- **Alle Variablen** können in Shortcuts verwendet werden und werden beim Einfügen sofort aufgelöst.
|
||||
- Auch längere Texte möglich.
|
||||

|
||||
|
||||
**Tipp**: Häufig verwendete Abkürzungen wie „pse", „rrr", „tnx", „73" als Shortcuts anlegen.
|
||||
Jeder Eintrag erzeugt eine Schaltfläche im Hauptfenster. Ein Klick hängt den konfigurierten Text an den vorhandenen Inhalt des Sendfeldes an. Ein bereits vorbereiteter Nachrichtentext wird dabei nicht gelöscht.
|
||||
|
||||
Enthält der Shortcut eine Variable, wird sie beim Einfügen aufgelöst. Aus
|
||||
|
||||
```text
|
||||
pse call me at MYQRGSHORT
|
||||
```
|
||||
|
||||
kann beispielsweise werden:
|
||||
|
||||
```text
|
||||
pse call me at 144.388
|
||||
```
|
||||
|
||||
Die exakten Einträge `MYQRG` und `SECONDQRG` werden als QRG-Schaltflächen hervorgehoben. Sie fügen die aktuelle QRG der ersten beziehungsweise zweiten Chat-Kategorie ein.
|
||||
|
||||
Auch der Shortcut
|
||||
|
||||
```text
|
||||
/SETNAME MYQRG
|
||||
```
|
||||
|
||||
wird hervorgehoben. Beim Anklicken wird `MYQRG` aufgelöst und der daraus entstehende Serverbefehl in das Sendfeld übernommen. Der Befehl wird nicht automatisch gesendet.
|
||||
|
||||
Die Reihenfolge der Einträge in den Einstellungen bestimmt die Reihenfolge der Schaltflächen im Hauptfenster. Bearbeitung, Sortierung und Speicherung sind unter [Konfiguration – Shortcut Settings](de-Konfiguration#shortcut-settings-schnellzugriff-schaltflächen) beschrieben.
|
||||
|
||||
---
|
||||
|
||||
## Snippets (Text-Bausteine)
|
||||
## Text-Snippets
|
||||
|
||||
Konfigurierbar in den Preferences → **Snippet Settings**.
|
||||
Snippets werden unter **Preferences → Snippet Settings** konfiguriert. Sie sind vor allem für wiederkehrende Nachrichten an eine bestimmte Station vorgesehen.
|
||||
|
||||
### Aufruf
|
||||
Snippets können aufgerufen werden:
|
||||
|
||||
- **Rechtsklick** auf ein Rufzeichen in der Benutzerliste
|
||||
- **Rechtsklick** in der CQ-Nachrichtentabelle
|
||||
- **Rechtsklick** in der PM-Nachrichtentabelle
|
||||
- **Tastaturkürzel**: `Ctrl+1` bis `Ctrl+0` für die ersten 10 Snippets
|
||||
- per Rechtsklick auf eine Station in der Benutzerliste,
|
||||
- per Rechtsklick auf eine öffentliche Nachricht,
|
||||
- per Rechtsklick auf eine Privatnachricht oder
|
||||
- mit `Ctrl+1` bis `Ctrl+0` für die ersten zehn Einträge der Snippet-Liste.
|
||||
|
||||
### Verhalten mit ausgewähltem Rufzeichen
|
||||
### Verwendung über das Kontextmenü
|
||||
|
||||
Wenn in der Benutzerliste ein Rufzeichen ausgewählt ist, wird der Snippet als **Privatnachricht** adressiert:
|
||||
Die Auswahl einer Station oder Nachricht bereitet normalerweise bereits den passenden `/cq`-Empfänger im Sendfeld vor. Das anschließend im Kontextmenü ausgewählte Snippet wird an diesen Text angehängt.
|
||||
|
||||
```
|
||||
/CQ RUFZEICHEN <Snippet-Text>
|
||||
Ein bereits vorhandener Nachrichtentext kann dadurch gezielt erweitert werden.
|
||||
|
||||
### Verwendung über die Tastatur
|
||||
|
||||
Ein mit `Ctrl+1` bis `Ctrl+0` aufgerufenes Snippet ersetzt den bisherigen Inhalt des Sendfeldes durch eine vollständig adressierte Nachricht:
|
||||
|
||||
```text
|
||||
/cq RUFZEICHEN Snippet-Text
|
||||
```
|
||||
|
||||
Anschließend kann mit **Enter** direkt gesendet werden – auch wenn das Sendfeld nicht den Fokus hat.
|
||||
Das vollständige sichtbare Rufzeichen einschließlich eines vorhandenen Suffixes bleibt erhalten. Für die ausgewählte Station `9A0BB-70` kann beispielsweise entstehen:
|
||||
|
||||
### Hardware-Makro-Tastatur
|
||||
```text
|
||||
/cq 9A0BB-70 pse ur qrg?
|
||||
```
|
||||
|
||||
*(Idee von IU3OAR, Gianluca Costantino)*
|
||||
KST4Contest behält intern auch die Chat-Kategorie der ausgewählten Station bei. Ein Snippet für `9A0BB-70` wird deshalb nicht versehentlich über die andere aktive Chat-Kategorie gesendet.
|
||||
|
||||
Die Tastenkombinationen `Ctrl+1` bis `Ctrl+0` können auf einer programmierbaren Makro-Tastatur belegt werden. Ein weiterer Tastendruck (auf eine „Enter"-Taste) sendet den Text sofort. Im Contest-Betrieb spart das erheblich Zeit.
|
||||
Ist keine Station ausgewählt oder existiert für die gedrückte Tastenkombination kein Snippet, wird nichts eingefügt.
|
||||
|
||||
### Vordefinierte Standard-Snippets
|
||||
Der vorbereitete Text wird nicht automatisch versendet:
|
||||
|
||||
Beim ersten Start werden einige Snippets vorbelegt, z. B.:
|
||||
- `Enter` oder **TX** sendet die Nachricht.
|
||||
- `Esc` leert das Sendfeld.
|
||||
|
||||
- `Hi OM, try sked?`
|
||||
- `I am calling cq ur dir, pse lsn to me at MYQRG`
|
||||
- `pse ur qrg?`
|
||||
- `rrr, I move to your qrg nw, pse ant dir me`
|
||||
### Zuordnung der Tastenkombinationen
|
||||
|
||||
Diese können in den Preferences angepasst oder gelöscht werden.
|
||||
Die Zuordnung folgt der Reihenfolge in der Snippet-Liste:
|
||||
|
||||
| Tastenkombination | Verwendeter Eintrag |
|
||||
|---|---:|
|
||||
| `Ctrl+1` | erster Eintrag |
|
||||
| `Ctrl+2` | zweiter Eintrag |
|
||||
| … | … |
|
||||
| `Ctrl+9` | neunter Eintrag |
|
||||
| `Ctrl+0` | zehnter Eintrag |
|
||||
|
||||
Die Tastenkombinationen können auch einer programmierbaren Makro-Tastatur zugewiesen werden. Die Idee zu dieser Bedienung stammt von IU3OAR, Gianluca Costantino.
|
||||
|
||||
KST4Contest legt keine verbindliche Liste von Standard-Snippets fest. Welche Texte sinnvoll sind, hängt vom eigenen Contestbetrieb und der verwendeten Betriebsart ab.
|
||||
|
||||
Bearbeitung, Sortierung und Speicherung sind unter [Konfiguration – Snippet Settings](de-Konfiguration#snippet-settings-text-snippets) beschrieben.
|
||||
|
||||
---
|
||||
|
||||
## Variablen
|
||||
|
||||
Variablen werden in geschriebenen Texten (Snippets, Shortcuts, Beacon, Sendfeld) durch ihre aktuellen Werte ersetzt. Einfach den Variablennamen **großgeschrieben** in den Text einfügen.
|
||||
Variablen sind reservierte Platzhalter innerhalb eines Nachrichtentextes. Sie müssen in Großbuchstaben geschrieben werden und unterscheiden zwischen Groß- und Kleinschreibung.
|
||||
|
||||
### MYQRG
|
||||
Variablen können verwendet werden in:
|
||||
|
||||
Wird durch die aktuelle Transceiverfrequenz ersetzt.
|
||||
- Shortcuts,
|
||||
- Snippets,
|
||||
- Beacon-Texten und
|
||||
- direkt eingegebenen oder eingefügten Nachrichtentexten.
|
||||
|
||||
- Quelle: TRX-Sync via UDP vom Logprogramm (wenn aktiviert)
|
||||
- Fallback: Manuell eingetragener Wert im MYQRG-Textfeld rechts neben dem Sendbutton
|
||||
- Format: `144.388.03`
|
||||
Bei einem Shortcut oder Snippet werden die Variablen bereits beim Einfügen in das Sendfeld aufgelöst. Direkt in das Sendfeld geschriebene oder eingefügte Variablen werden unmittelbar vor der Übernahme in die Sendewarteschlange aufgelöst.
|
||||
|
||||
**Beispiel**: `calling cq at MYQRG` → `calling cq at 144.388.03`
|
||||
Stationsbezogene Variablen verwenden immer die aktuell ausgewählte Station. KST4Contest leitet diese Station nicht aus einem von Hand in den Nachrichtentext geschriebenen `/cq`-Empfänger ab.
|
||||
|
||||
### MYQRGSHORT
|
||||
---
|
||||
|
||||
Wie MYQRG, aber nur die ersten 7 Zeichen.
|
||||
## Globale Variablen
|
||||
|
||||
- Format: `144.388`
|
||||
Globale Variablen benötigen keine ausgewählte Gegenstation.
|
||||
|
||||
**Beispiel**: `qrg: MYQRGSHORT` → `qrg: 144.388`
|
||||
| Variable | Ersetzter Wert |
|
||||
|---|---|
|
||||
| `MYQRG` | aktuelle QRG der ersten beziehungsweise primären Chat-Kategorie |
|
||||
| `MYQRGSHORT` | erste sieben Zeichen von `MYQRG` |
|
||||
| `SECONDQRG` | aktuelle QRG der zweiten Chat-Kategorie |
|
||||
| `MYLOCATOR` | vollständiger Locator der eigenen Station |
|
||||
| `MYLOCATORSHORT` | erste vier Zeichen des eigenen Locators |
|
||||
| `MYCALL` | konfiguriertes eigenes Rufzeichen |
|
||||
| `MYQTF` | aktuelle Antennenrichtung als numerischer Wert in Grad |
|
||||
|
||||
### MYLOCATOR
|
||||
Beispiel:
|
||||
|
||||
Wird durch den eigenen Maidenhead-Locator (6-stellig) ersetzt.
|
||||
```text
|
||||
cq at MYQRGSHORT, qtf MYQTF, loc MYLOCATOR
|
||||
```
|
||||
|
||||
- Format: `JO51IJ`
|
||||
kann aufgelöst werden zu:
|
||||
|
||||
**Beispiel**: `my loc: MYLOCATOR` → `my loc: JO51IJ`
|
||||
```text
|
||||
cq at 144.388, qtf 135, loc JO51IJ
|
||||
```
|
||||
|
||||
### MYLOCATORSHORT
|
||||
### QRG-Variablen
|
||||
|
||||
Wie MYLOCATOR, aber nur die ersten 4 Zeichen.
|
||||
`MYQRG` enthält die QRG der ersten Chat-Kategorie. Der Wert kann aus der TRX-Synchronisation des Logprogramms oder aus dem manuell bearbeiteten QRG-Feld stammen.
|
||||
|
||||
- Format: `JO51`
|
||||
`MYQRGSHORT` verwendet denselben Wert, beschränkt ihn aber auf die ersten sieben Zeichen:
|
||||
|
||||
**Beispiel**: `loc: MYLOCATORSHORT` → `loc: JO51`
|
||||
```text
|
||||
144.388.03 → 144.388
|
||||
```
|
||||
|
||||
`SECONDQRG` enthält die QRG der zweiten Chat-Kategorie. Die Auswahl einer Station aus dem zweiten Chat verändert die Bedeutung von `MYQRG` nicht. Soll ausdrücklich die QRG der zweiten Kategorie eingesetzt werden, muss `SECONDQRG` verwendet werden.
|
||||
|
||||
### Locator-Variablen
|
||||
|
||||
`MYLOCATOR` übernimmt den vollständigen konfigurierten Locator der eigenen Station:
|
||||
|
||||
```text
|
||||
JO51IJ
|
||||
```
|
||||
|
||||
`MYLOCATORSHORT` verwendet nur die ersten vier Zeichen:
|
||||
|
||||
```text
|
||||
JO51
|
||||
```
|
||||
|
||||
### MYQTF
|
||||
|
||||
`MYQTF` übernimmt die aktuelle, in KST4Contest hinterlegte Antennenrichtung als numerischen Winkel in Grad.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
ant MYQTF deg
|
||||
```
|
||||
|
||||
kann werden zu:
|
||||
|
||||
```text
|
||||
ant 135 deg
|
||||
```
|
||||
|
||||
Die Richtung wird nicht in Himmelsrichtungen wie `north`, `north-east` oder `south-west` umgewandelt.
|
||||
|
||||
---
|
||||
|
||||
## Variablen für die ausgewählte Station
|
||||
|
||||
Diese Variablen benötigen eine ausgewählte Gegenstation:
|
||||
|
||||
| Variable | Ersetzter Wert |
|
||||
|---|---|
|
||||
| `QRZNAME` | Name der ausgewählten Station oder deren vollständiges Rufzeichen, wenn kein Name verfügbar ist |
|
||||
| `FIRSTAP` | Beschreibung und Ankunftszeit des ersten von AirScout gemeldeten Flugzeugs |
|
||||
| `SECONDAP` | Beschreibung und Ankunftszeit des zweiten von AirScout gemeldeten Flugzeugs |
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
Hi QRZNAME, FIRSTAP, pse lsn at MYQRGSHORT
|
||||
```
|
||||
|
||||
kann werden zu:
|
||||
|
||||
```text
|
||||
Hi David, a very big AP in 2 min, pse lsn at 144.388
|
||||
```
|
||||
|
||||
### QRZNAME
|
||||
|
||||
Wird durch den **Namen** der aktuell ausgewählten Station aus dem Chat-Namenfeld ersetzt.
|
||||
|
||||
**Beispiel**: `Hi QRZNAME, sked?` → `Hi Gianluca, sked?`
|
||||
KST4Contest verwendet den Namen aus dem Namensfeld der ausgewählten Station. Ist dort kein verwendbarer Name vorhanden, wird stattdessen das vollständige sichtbare Rufzeichen eingesetzt.
|
||||
|
||||
### FIRSTAP
|
||||
|
||||
Wird durch Daten des ersten reflektierbaren Flugzeugs zur ausgewählten Station ersetzt (sofern vorhanden).
|
||||
Ist ein AirScout-Kandidat verfügbar, enthält `FIRSTAP` dessen Beschreibung und die voraussichtliche Zeit bis zum Reflexionsfenster.
|
||||
|
||||
- Bedingung: AirScout ist aktiv und ein Flugzeug ist verfügbar.
|
||||
- Format-Beispiel: `a very big AP in 1 min`
|
||||
Beispiel:
|
||||
|
||||
**Beispiel**: `AP info: FIRSTAP` → `AP info: a very big AP in 1 min`
|
||||
```text
|
||||
a very big AP in 2 min
|
||||
```
|
||||
|
||||
Ist für die ausgewählte Station kein Flugzeug verfügbar, wird eingesetzt:
|
||||
|
||||
```text
|
||||
no ap available
|
||||
```
|
||||
|
||||
### SECONDAP
|
||||
|
||||
Wie FIRSTAP, aber für das zweite verfügbare Flugzeug.
|
||||
`SECONDAP` verwendet den zweiten verfügbaren AirScout-Kandidaten.
|
||||
|
||||
- Format-Beispiel: `Next big AP in 9 min`
|
||||
Beispiel:
|
||||
|
||||
**Beispiel**: `also: SECONDAP` → `also: Next big AP in 9 min`
|
||||
```text
|
||||
Next big AP in 9 min
|
||||
```
|
||||
|
||||
### MYQTF *(geplant für v1.3)*
|
||||
Ist kein zweiter Kandidat vorhanden, wird `SECONDAP` durch einen leeren Text ersetzt.
|
||||
|
||||
Wird durch die aktuelle Antennenrichtung in Worten ersetzt (z. B. `north`, `north east`, `east`, …).
|
||||
### Verhalten ohne ausgewählte Station
|
||||
|
||||
- Quelle: Winkelwert im MYQTF-Eingabefeld (rechts neben dem MYQRG-Feld)
|
||||
Ist keine Station ausgewählt, bleiben `QRZNAME`, `FIRSTAP` und `SECONDAP` im Text sichtbar. KST4Contest entfernt diese Platzhalter nicht automatisch.
|
||||
|
||||
Ein sichtbarer, nicht aufgelöster Platzhalter ist eindeutiger als eine formal vollständige Nachricht, in der unbemerkt eine wichtige Information fehlt. Vor dem Senden sollte deshalb geprüft werden, ob die richtige Station ausgewählt ist und alle benötigten Variablen aufgelöst wurden.
|
||||
|
||||
---
|
||||
|
||||
## Variablen im Beacon
|
||||
## Variablen im Beacon
|
||||
|
||||
Ein öffentlicher Beacon besitzt keine ausgewählte Gegenstation. Deshalb können hier ausschließlich Variablen verwendet werden, die nur von der eigenen Station und ihrer aktuellen Konfiguration abhängen:
|
||||
Ein öffentlicher Beacon besitzt keine ausgewählte Gegenstation. Deshalb können dort ausschließlich globale Variablen verwendet werden:
|
||||
|
||||
| Variable | Wert im Beacon |
|
||||
|---|---|
|
||||
| `MYQRG` | aktuelle QRG der ersten Chat-Kategorie |
|
||||
| `MYQRGSHORT` | auf sieben Zeichen gekürzte QRG der ersten Kategorie |
|
||||
| `SECONDQRG` | aktuelle QRG der zweiten Chat-Kategorie |
|
||||
| `MYLOCATOR` | eigener vollständiger Locator |
|
||||
| `MYLOCATORSHORT` | eigener vierstelliger Locator |
|
||||
| `MYCALL` | eigenes Rufzeichen |
|
||||
| `MYQTF` | aktuelle Antennenrichtung |
|
||||
- `MYQRG`
|
||||
- `MYQRGSHORT`
|
||||
- `SECONDQRG`
|
||||
- `MYLOCATOR`
|
||||
- `MYLOCATORSHORT`
|
||||
- `MYCALL`
|
||||
- `MYQTF`
|
||||
|
||||
`QRZNAME`, `FIRSTAP` und `SECONDAP` benötigen eine ausgewählte Station. In einem öffentlichen Beacon werden sie daher nicht aufgelöst.
|
||||
`QRZNAME`, `FIRSTAP` und `SECONDAP` dürfen in einem Beacon nicht verwendet werden.
|
||||
|
||||
Eine zweckmäßige Konfiguration ist beispielsweise:
|
||||
Eine mögliche Vorlage für die erste Chat-Kategorie ist:
|
||||
|
||||
```
|
||||
calling cq at MYQRG, loc MYLOCATOR, GL all!
|
||||
```text
|
||||
calling cq at MYQRGSHORT, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Die globalen Variablen werden bei jedem Timer-Lauf neu ausgewertet. Dadurch kann eine vom Logprogramm aktualisierte QRG bereits in der nächsten Beacon-Nachricht erscheinen.
|
||||
Verwendet die zweite Chat-Kategorie eine andere QRG, muss deren Vorlage `SECONDQRG` enthalten:
|
||||
|
||||
Der vollständig aufgelöste Nachrichtentext darf höchstens 120 Zeichen enthalten. Weitere Angaben zum gemeinsamen Intervall und zum Verhalten beider Chat-Kategorien stehen unter [Konfiguration – Beacon Settings](de-Konfiguration#beacon-settings-automatischer-beacon).
|
||||
```text
|
||||
calling cq at SECONDQRG, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Die globalen Variablen werden bei jedem Timer-Lauf erneut ausgewertet. Eine inzwischen vom Logprogramm aktualisierte QRG kann dadurch bereits in der nächsten Beacon-Nachricht erscheinen.
|
||||
|
||||
Der vollständig aufgelöste Beacon-Text:
|
||||
|
||||
- muss mindestens ein gültiges Zeichen enthalten,
|
||||
- darf höchstens 120 Zeichen lang sein,
|
||||
- darf das Protokoll-Trennzeichen `|` nicht enthalten und
|
||||
- darf keine Zeilenumbrüche enthalten.
|
||||
|
||||
Ist der Text beim vorgesehenen Versand leer oder ungültig, wird der betreffende Beacon-Lauf ausgelassen.
|
||||
|
||||
Intervall, Aktivierung und Verhalten beider Kategorien sind unter [Konfiguration – Beacon Settings](de-Konfiguration#beacon-settings-automatischer-beacon) beschrieben.
|
||||
|
||||
---
|
||||
## Beispiel-Workflow mit Makros im Contest
|
||||
|
||||
1. Station in der Benutzerliste auswählen → Rufzeichen ist nun vorausgewählt.
|
||||
2. `Ctrl+1` drücken → Snippet „Hi OM, try sked?" wird als PM adressiert.
|
||||
3. Enter drücken → Nachricht wird gesendet.
|
||||
4. Station antwortet mit Frequenz → QRG-Spalte wird automatisch befüllt.
|
||||
5. `Ctrl+2` → Snippet „I am calling cq ur dir, pse lsn to me at 144.388" (MYQRG aufgelöst).
|
||||
6. Enter → Gesendet.
|
||||
## Beispiel für einen Snippet-Workflow
|
||||
|
||||
Ohne manuelle Tipparbeit, ohne Fehler, ohne Unterbrechung des CQ-Rufens.
|
||||
Als erstes Snippet ist beispielsweise konfiguriert:
|
||||
|
||||
```text
|
||||
Hi QRZNAME, pse sked? I call at MYQRGSHORT
|
||||
```
|
||||
|
||||
Der Ablauf kann dann folgendermaßen aussehen:
|
||||
|
||||
1. In der Benutzerliste wird `DL1ABC-432` ausgewählt.
|
||||
2. `Ctrl+1` wird gedrückt.
|
||||
3. KST4Contest bereitet die adressierte Nachricht vor und löst die Variablen auf.
|
||||
4. Der vollständige Text wird im Sendfeld geprüft.
|
||||
5. Falls die Gegenstation eine andere QRG vorgeschlagen hat, wird der Text entsprechend angepasst.
|
||||
6. `Enter` oder **TX** sendet die Nachricht.
|
||||
|
||||
Das Ergebnis kann beispielsweise lauten:
|
||||
|
||||
```text
|
||||
/cq DL1ABC-432 Hi Peter, pse sked? I call at 432.088
|
||||
```
|
||||
|
||||
Das vollständige Rufzeichen bestimmt den Empfänger. Die ausgewählte Chat-Kategorie bestimmt den Versandweg. Die Variablen verringern die wiederholte Texteingabe, entscheiden aber nicht, ob die eingesetzten Informationen noch zur aktuellen Betriebssituation passen.
|
||||
|
||||
---
|
||||
|
||||
## Grenzen der Variablenauflösung
|
||||
|
||||
Variablen geben den Informationsstand wieder, den KST4Contest im Moment der Auflösung besitzt.
|
||||
|
||||
Dabei ist insbesondere zu beachten:
|
||||
|
||||
- Eine vom Logprogramm gelieferte QRG kann sich inzwischen geändert haben.
|
||||
- Eine manuell eingetragene QRG bleibt aktiv, bis sie erneut geändert wird.
|
||||
- `MYQRG` bleibt die QRG der primären Kategorie, auch wenn eine Station aus der zweiten Kategorie ausgewählt wurde.
|
||||
- Die ausgewählte Station kann von einem manuell eingegebenen `/cq`-Empfänger abweichen.
|
||||
- AirScout kann für die betreffende Strecke keine aktuellen Flugzeugdaten liefern.
|
||||
- Stationsbezogene Variablen bleiben sichtbar, wenn keine Station ausgewählt ist.
|
||||
- Der eingefügte Text wird nicht automatisch auf seine betriebliche Richtigkeit geprüft.
|
||||
|
||||
Das Sendfeld bleibt deshalb nach dem Einfügen eines Shortcuts oder Snippets bearbeitbar. Die Variablen vermeiden wiederholte Eingaben; die abschließende Prüfung bleibt beim Operator.
|
||||
|
||||
---
|
||||
|
||||
## Verwendung im Contest
|
||||
|
||||
Shortcuts, Snippets und Variablen sind einzelne Werkzeuge innerhalb des laufenden Betriebs. Ihr Zusammenspiel mit CQ-Betrieb, Stationsauswahl, Skeds, QRG-Wechseln und Log-Synchronisation ist unter [Contest-Workflow mit KST4Contest](de-Contest-Workflow) beschrieben.
|
||||
|
||||
|
After Width: | Height: | Size: 571 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
> 🇬🇧 You are reading the English version | 🇩🇪 [Deutsche Version](de-AirScout-Integration)
|
||||
|
||||
AirScout (by DL2ALF) is a program for detecting aircraft for aircraft scatter operation. KST4Contest is tightly integrated with AirScout and shows reflectable aircraft directly in the user list.
|
||||
AirScout (by DL2ALF) calculates aircraft-scatter opportunities from current aircraft positions. KST4Contest receives these results and displays aircraft suitable for the path to each remote station directly in the user list.
|
||||
|
||||
> **Aircraft Scatter** enables very long-distance communication on VHF and higher – even for stations with low altitude above sea level or unfavourable topographic conditions.
|
||||
|
||||
@@ -15,9 +15,9 @@ Download AirScout from:
|
||||
|
||||
---
|
||||
|
||||
## Aircraft Data Feeds (ADSB)
|
||||
## Aircraft Data Feeds (ADS-B)
|
||||
|
||||
Public aircraft data feeds on the internet are often unreliable and limited in use. A recommended alternative is the dedicated ADSB feed service provided by **OV3T (Thomas)**:
|
||||
Public aircraft data feeds on the internet are often unreliable and of limited use. A recommended alternative is the dedicated ADS-B feed service provided by **OV3T (Thomas)**:
|
||||
|
||||
- https://airscatter.dk/
|
||||
- https://www.facebook.com/groups/825093981868542
|
||||
@@ -28,7 +28,7 @@ An account is required for this service. Please consider donating to Thomas –
|
||||
|
||||
## Setting Up AirScout
|
||||
|
||||
### Step 1: Configure the ADSB Feed in AirScout
|
||||
### Step 1: Configure the ADS-B Feed in AirScout
|
||||
|
||||
1. Start AirScout.
|
||||
2. Enter your OV3T feed account details (username, password, URL) in the AirScout settings.
|
||||
@@ -85,32 +85,40 @@ Otherwise, it may result in incorrect AP data.
|
||||
|
||||
## AP Column in the User List
|
||||
|
||||
After setup, an **AP column** appears in the user list showing up to two reflectable aircraft per station.
|
||||
After setup, an **AP column** appears in the user list. For each station, it shows the arrival time and the AirScout reflection potential of the first two suitable aircraft.
|
||||
|
||||
Example display:
|
||||
|
||||
| Station | AP Info |
|
||||
|---|---|
|
||||
| DF9QX | 2 Planes: 0 min / 0 min, 100% each |
|
||||
| F5DYD | 2 Planes: 14 min / 31 min, 50% each |
|
||||
| DF9QX | 0 (100%) / 0 (100%) |
|
||||
| F5DYD | 14 (50%) / 31 (50%) |
|
||||
|
||||
The number before the brackets is the number of minutes remaining until the calculated opportunity. The percentage is the reflection potential reported by AirScout. It is not a QSO probability.
|
||||
|
||||
AP information is also available in the **private messages window**.
|
||||
|
||||
The percentage indicates the reflection potential (aircraft size, altitude, distance).
|
||||
## Effect on the Priority Score and Timeline
|
||||
|
||||
At least one aircraft reported as reachable by AirScout raises the station's Priority Score. An opportunity expected in zero, one or two minutes receives additional time-dependent weighting. AirScout is only one factor alongside Worked status, available bands, QRB, antenna direction, chat activity and skeds.
|
||||
|
||||
The AP and sked timeline uses the Priority Score to select interesting stations and places the next suitable aircraft-scatter opportunity on the time axis. The reflection potential also controls how the AP marker is displayed. The timeline remains a preview and does not guarantee a QSO.
|
||||
|
||||
---
|
||||
|
||||
## AP Variables in Messages
|
||||
|
||||
Aircraft data can be inserted directly into messages:
|
||||
Aircraft data for the selected station can be inserted directly into shortcuts, snippets and other station-specific messages:
|
||||
|
||||
- `FIRSTAP` → e.g. `a very big AP in 1 min`
|
||||
- `SECONDAP` → e.g. `Next big AP in 9 min`
|
||||
|
||||
Details: [Macros and Variables](Macros-and-Variables#variables)
|
||||
Details: [Macros and Variables](en-Macros-and-Variables#variables)
|
||||
|
||||
Because these values require a selected remote station, `FIRSTAP` and `SECONDAP` are not available as global beacon variables.
|
||||
|
||||
---
|
||||
|
||||
## "Show Path in AirScout" Button
|
||||
|
||||
In the user list there is a button with an arrow showing the direction (QTF) to the selected station. Clicking it maximises AirScout and shows the path with reflectable aircraft to the selected contact.
|
||||
In the user list there is a button with an arrow showing the direction (QTF) to the selected station. Clicking it maximises the external AirScout window and displays the path to the selected remote station together with the calculated aircraft-scatter opportunities. The button does not start a separate terrain or propagation calculation in KST4Contest.
|
||||
|
||||
@@ -8,15 +8,16 @@ Published Stable versions and their application packages are available under [Gi
|
||||
|
||||
---
|
||||
|
||||
## v1.42 – Nightly / in development
|
||||
## v1.42.0 (2026-08-22)
|
||||
|
||||
> Status of this section: 10 August 2026.
|
||||
> v1.42 is not a published Stable release yet. Further changes may be added before release.
|
||||
**Shared band context, session-based ON4KST connection and signed macOS packages**
|
||||
|
||||
v1.42 brings several previously separate calculations together. Band information, Worked status, NOT-QRV marks, callsign suffixes and frequencies are now used more consistently by the user list, station map, priority calculation and external interfaces.
|
||||
|
||||
### Added
|
||||
|
||||
- **Visible ON4KST connection state:** A compact `LINK` indicator in the main window displays the actual state of the ON4KST connection. Green means fully authenticated and synchronised, yellow indicates connection setup or synchronisation, and red indicates a lost connection, an error or the delay before the next connection attempt.
|
||||
|
||||
- **Shared band-opportunity calculation:** A central `BandOpportunityResolver` evaluates recent QRGs, band designators in station names, active callsign variants, Worked information and NOT-QRV marks. The user list, **New bands**, band-upgrade hint, Priority Score, station map and automatic band selection now use the same basis.
|
||||
|
||||
- **Extended band status display:** The band columns now distinguish:
|
||||
@@ -33,6 +34,8 @@ v1.42 brings several previously separate calculations together. Band information
|
||||
|
||||
- **Manual QTF input:** The current antenna direction can be changed directly in KST4Contest when PSTRotator is not being used.
|
||||
|
||||
- **`MYQTF` message variable:** The current antenna direction can be inserted as a numeric angle in degrees into shortcuts, snippets and other supported message texts.
|
||||
|
||||
- **Filter reset:** A dedicated reset button reliably removes the active user-list filter predicates.
|
||||
|
||||
- **Map clustering:** Stations close to each other are grouped at lower zoom levels. The selected station and relevant direction opportunities remain individually visible.
|
||||
@@ -41,7 +44,13 @@ v1.42 brings several previously separate calculations together. Band information
|
||||
|
||||
### Changed
|
||||
|
||||
- **More precise QRG recognition:** Complete and relative frequency references continue to be recognised. Bare three-digit numbers are treated as QRGs only when a frequency context is available. Signal reports, band designators and unrelated numbers therefore produce fewer false frequencies.
|
||||
- **Unified DX Cluster line format:** Local spots now use a fixed, DXSpider-compatible 75-character payload line with the DX callsign in column 27, a 30-character comment field and UTC time in column 71. The layout remains stable for microwave frequencies up to 24 GHz. Overlong DX callsigns are rejected and logged instead of being silently truncated; AirScout and test comments are correspondingly more compact.
|
||||
|
||||
- **Session-based ON4KST connection lifecycle:** Each socket, reader, writer, message bus and queue now belongs to an explicitly identified connection session. Delayed threads from an obsolete connection can therefore no longer process data or close its replacement. `ONLINE` is reported only after the login has been accepted and all requested user lists have been received. Connection setup, login and synchronisation use bounded timeouts, while heartbeats, missing inbound traffic, EOF and read or write failures trigger controlled reconnect attempts with backoff where appropriate.
|
||||
|
||||
- **Validated ON4KST protocol commands:** Outgoing frames are built centrally and checked for valid categories, locators and prohibited frame delimiters. Because ON4KST maintains one locator per TCP session, the main locator is used for both chat categories and a conflicting secondary configuration is logged instead of sending contradictory commands to the server.
|
||||
|
||||
- **More precise QRG recognition:** Complete frequencies are also recognised without a decimal separator, with the final three digits interpreted as the kHz part. Relative frequencies remain unchanged, and bare three-digit numbers still require recognisable frequency context. Signal reports, band designators and unrelated numbers therefore produce fewer false frequencies.
|
||||
|
||||
- **Station-specific frequency context:** For relative QRGs, KST4Contest first uses a band context for the same station which is no more than 30 minutes old. The globally configured fallback band is used only when this context is unavailable.
|
||||
|
||||
@@ -65,7 +74,7 @@ v1.42 brings several previously separate calculations together. Band information
|
||||
|
||||
- **Exact sked targets:** The timeline and automatic reminders use the complete visible KST callsign. A sked for `DN9APW-2` is not accidentally sent to another variant of the same base callsign.
|
||||
|
||||
- **Reworked beacon and automatic replies:** Both chat categories use one shared timer while retaining separate enable switches and message texts. The minimum permitted interval is two minutes and message texts are limited to 120 characters. The stored beacon state is restored from the configuration at startup.
|
||||
- **Reworked beacon and automatic replies:** Both chat categories use one shared timer while retaining separate enable switches and message texts. The minimum permitted interval is one minute and message texts are limited to 120 characters. The stored beacon state is restored from the configuration at startup.
|
||||
|
||||
- **Central variable resolution:** Message variables used by beacons, shortcuts, snippets and other generated text are processed by one shared resolver.
|
||||
|
||||
@@ -75,10 +84,28 @@ v1.42 brings several previously separate calculations together. Band information
|
||||
|
||||
- **DXLog full-log import:** In addition to `contactinfo`, the UCXLog-compatible UDP listener processes `contactreplace`. This allows a complete log broadcast by DXLog.net to be imported.
|
||||
|
||||
- **Consistent logger band values:** Numeric, metre and centimetre values from UCXLog-compatible QSO packets and Win-Test band IDs are normalised once and then used consistently for Worked marks and worked grid squares. In particular, `2320`, `5760` and `10368` now reliably set their existing band marks. A missing or unknown band continues to set only the global Worked status.
|
||||
|
||||
- **Defined Simplelogfile behaviour:** The selected text file is evaluated once per minute using a fixed callsign pattern. Matches set the global Worked status for all active variants of the base callsign but are not persisted in SQLite. A missing file is created, and read or creation errors do not terminate the periodic task. A database reset does not change the file, so callsigns contained in it are marked as worked again during the next evaluation.
|
||||
|
||||
- **Guarded automatic QRG updates:** `MYQRG` is updated only by an enabled interface which actually supplies valid `RadioInfo` or Win-Test `STATUS` packets. An enabled source which provides no data does not remove the need for a functional check or manual QRG maintenance.
|
||||
|
||||
- **Improved version comparison:** Versions are compared semantically so that patch releases and Nightly versions are not misclassified by conversion to a floating-point number.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Reliable initial user list:** Invalid or incomplete `UA0` member records are rejected and logged individually without preventing alphabetically following members from being processed. Valid entries are staged per category and published as one complete snapshot when the first corresponding `UE` end marker is received.
|
||||
|
||||
- **Persisted Worked state during initial-list setup:** At the end of each initial ON4KST user list, the SQLite state is loaded once and applied to the new chat members before publication. This covers both categories, reconnects and every active variant of a base callsign.
|
||||
|
||||
- **User list disappearing after login:** ON4KST may send additional `UE` frames for the same category after name, state or other live updates. Repeated end markers are now detected and ignored so that an already populated user list cannot be replaced by an empty snapshot.
|
||||
|
||||
- **Failed initial connection and lost sockets:** An unavailable server during startup no longer sends KST4Contest into an endless or busy-wait loop. The user interface remains responsive and further attempts use bounded reconnect backoff. Sockets closed by the server, or connections without inbound traffic for an excessive period, are also detected reliably.
|
||||
|
||||
- **Message-bus diagnostics:** Correctly processed ON4KST frames are no longer reported additionally as `Critical, detected unhandled Chatmessage`. Only genuinely unknown frames reach the fallback diagnostic branch.
|
||||
|
||||
- **Password in diagnostic output:** The ON4KST password is no longer written in plain text to the console or error log during connection setup.
|
||||
|
||||
- **Long-running station-selection failure:** Chat members managed by the message thread have been decoupled from the JavaFX view. Simultaneous data and table updates therefore no longer cause broken selection models or concurrent-modification problems after longer runtimes.
|
||||
|
||||
- **No phantom chat members from UM3:** Historical or additional server messages no longer create user-list entries for stations which are not actually logged into the chat.
|
||||
@@ -107,6 +134,10 @@ v1.42 brings several previously separate calculations together. Band information
|
||||
|
||||
- Nightly packages are built automatically from the current `main` branch. Stable and Beta releases use predictable asset names for the supported platforms.
|
||||
|
||||
- **Signed and notarized macOS packages:** The DMG files for Apple Silicon and Intel are signed with an Apple Developer ID and notarized by Apple, with the notarization ticket stapled into the DMG. The first launch now works by double-clicking, without the previous detour through **Open** in the context menu, and the check also succeeds without an internet connection. This applies to Nightly, Beta and Stable packages alike. The Windows packages remain unsigned.
|
||||
|
||||
- **Correct bundle identifier and version on macOS:** The application now identifies itself as `de.x08.KST4Contest` rather than `kst4contest.view`, and carries its actual version number in the bundle. Previously every release reported version `1.0` in Finder's **Get Info** panel. Existing settings are unaffected, because KST4Contest stores its data in `~/.praktiKST/` rather than keying it to the bundle identifier.
|
||||
|
||||
### Known limitations
|
||||
|
||||
- The active terrain provider uses Open-Meteo with Copernicus GLO-90 data and no more than 100 elevation samples per path.
|
||||
@@ -119,6 +150,8 @@ v1.42 brings several previously separate calculations together. Band information
|
||||
|
||||
- More detailed configuration of station height, frequency and K factor is being tracked in [Issue #74](https://github.com/praktimarc/kst4contest/issues/74).
|
||||
|
||||
The published version is available as [Release v1.42.0](https://github.com/praktimarc/kst4contest/releases/tag/v1.42.0).
|
||||
|
||||
---
|
||||
|
||||
## v1.41.1 (2026-07-08)
|
||||
@@ -389,7 +422,6 @@ First publicly released version. Core features:
|
||||
|
||||
## Planned Features
|
||||
|
||||
- `MYQTF` variable (own antenna direction as text)
|
||||
- ~~Lifetime for worked status (automatic reset)~~ ✅ **Implemented in v1.40** (3-day lifetime, no manual reset needed anymore)
|
||||
- Filtering the "Cluster & QSO of others" window to own QTF
|
||||
- Further topography-based calculations for direction warnings
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
After the first start, the **settings window** opens – this is the central starting point for all configuration. It is recommended to keep the settings window open during operation (e.g. to quickly toggle the beacon on and off).
|
||||
|
||||
> **Important**: Always click **"Save Settings"** after any change! Settings are stored in `~/.praktikst/preferences.xml` on Linux and in `%USERPROFILE%\.praktikst\preferences.xml` (or `C:\Users\<Username>\.praktikst\preferences.xml`) on Windows. From v1.21 onwards, window sizes and divider positions are also saved when you click Save.
|
||||
> **Important**: Always click **"Save Settings"** after any change! Settings are stored in `~/.praktiKST/preferences.xml` on Linux and macOS and in `%USERPROFILE%\.praktiKST\preferences.xml` (or `C:\Users\<Username>\.praktiKST\preferences.xml`) on Windows. From v1.21 onwards, window sizes and divider positions are also saved when you click Save.
|
||||
|
||||
---
|
||||
|
||||
@@ -14,14 +14,15 @@ After the first start, the **settings window** opens – this is the central sta
|
||||
|
||||
### Login and Chat Categories
|
||||
|
||||
Enter your ON4KST chat credentials here (callsign and password).
|
||||
Also, select the **primary chat category** (e.g., IARU Region 1 VHF/Microwave).
|
||||
Enter the one local login callsign and password used for the ON4KST chat. Also select the **primary chat category** (e.g. IARU Region 1 VHF/Microwave).
|
||||
|
||||
With the option for a **second chat** (Multi-Channel Login), you can log in to another category simultaneously (e.g., UHF/SHF). Both chats will then be monitored in parallel. You can optionally specify a different login name for the second chat (useful for Opposite Station Multi-Callsign Logging).
|
||||
The **second chat** option (Multi-Channel Login) adds another category (e.g. UHF/SHF) to the same ON4KST TCP session through Single Sign-on. KST4Contest does not open a second TCP connection and does not use another local login callsign or password.
|
||||
|
||||
**Name in Chat 2** configures only the visible, category-specific name field for the second category. It is neither another login nor a message destination. The visible name field, message context, QRG and beacon remain separate for each category. **Opposite Station Multi-Callsign Login Tagging**, by contrast, refers exclusively to remote stations which appear in the chat under several complete visible callsign variants.
|
||||
|
||||
### Callsign and Locator
|
||||
|
||||
Enter your own callsign and Maidenhead locator (6 characters, e.g., `JN49IJ`). These values are needed for distance and direction calculations.
|
||||
Enter your own callsign and Maidenhead locator (6 characters, e.g. `JN49IJ`). The locator is not part of authentication. ON4KST shares it between both categories in the one TCP session, and KST4Contest also uses it for distance and direction calculations.
|
||||
|
||||
### Active Bands
|
||||
|
||||
@@ -40,93 +41,334 @@ After a change, click **Save Settings** and restart KST4Contest. Band columns an
|
||||
|
||||
### Antenna Beamwidth
|
||||
|
||||
Enter a realistic value for your antenna's beamwidth (in degrees). This value is used for the [Sked Direction Highlighting](Features#sked-direction-highlighting). A test value of 50° has proven effective; DM5M uses quads with 69°.
|
||||
Enter the complete horizontal beamwidth of the local antenna in degrees. KST4Contest applies half of this value to either side of the selected or derived antenna direction. A configured value of `70°` therefore produces a corridor of `±35°`.
|
||||
|
||||
> **Do not** enter fantasy values – the direction calculations will become useless.
|
||||
The value is used for:
|
||||
|
||||
- the QTF filter in the user list;
|
||||
- the display of the local antenna corridor; and
|
||||
- the assumed beamwidth of a remote station when [deriving directional opportunities](en-Features#directional-opportunities-from-directed-messages).
|
||||
|
||||
The final use is deliberately an approximation. ON4KST transmits neither the antenna being used nor its beamwidth. KST4Contest therefore uses the local value as a practical assumption for the remote station.
|
||||
|
||||
Choose a realistic value for the actual station setup. A value which is too large produces many geometrical matches with little practical meaning. A value which is too small may hide useful directional opportunities.
|
||||
|
||||
### Default Maximum QRB
|
||||
|
||||
Maximum distance (in km) for which direction warnings should be triggered. A realistic value for DM5M is 900 km. Stations farther away are ignored for highlighting purposes.
|
||||
Enter the maximum distance in kilometres within which KST4Contest should consider directional opportunities. The relevant distance is between the local station and the sender of the directed message, not between sender and receiver.
|
||||
|
||||
If the sender is farther away, the situation is neither highlighted nor forwarded as an automatic directional opportunity to the local DX Cluster server, even if the calculated angle would match.
|
||||
|
||||
### Path Analysis and Link Budget
|
||||
|
||||
The station map uses several values from the station settings for its path analysis. These values describe the local station setup and, where no individual information is available, an assumed setup for the remote station.
|
||||
The station map uses several values from the **Station** tab to calculate the terrain profile and link budget for the selected remote station. Enter the local station data as realistically as possible. The remote-station values remain global assumptions unless more accurate information is available.
|
||||
|
||||
The following settings are used:
|
||||
| Setting | Use |
|
||||
|---|---|
|
||||
| **Own antenna height AGL** | Height of the local antenna above the surrounding terrain in metres |
|
||||
| **Own TX power W** | Local transmit power in watts |
|
||||
| **Own ant. gain dBi** | Gain of the local antenna in dBi |
|
||||
| **DX OM TX power W** | Assumed transmit power of the remote station in watts |
|
||||
| **DX OM ant. gain dBi** | Assumed antenna gain of the remote station in dBi |
|
||||
|
||||
- **Own antenna height AGL [m]** specifies the height of the local antenna above the surrounding terrain. The value is *Above Ground Level*, not height above sea level. KST4Contest adds it to the terrain elevation at the local station.
|
||||
- **Own TX power [W]** specifies the transmit power of the local station in watts.
|
||||
- **Own ant. gain [dBi]** specifies the antenna gain of the local station in dBi.
|
||||
- **DX OM TX power [W]** specifies the transmit power assumed for the remote station.
|
||||
- **DX OM ant. gain [dBi]** specifies the antenna gain assumed for the remote station.
|
||||
**AGL** means *Above Ground Level*. Do not enter the height above sea level. The terrain elevation at the local station already comes from the elevation profile; KST4Contest adds the configured antenna height to this value.
|
||||
|
||||
KST4Contest currently uses a fixed antenna height of 10 metres above the local terrain for the remote station. The remote power and antenna-gain values are global assumptions. They cannot replace individually known station data, but they provide a consistent basis when no better information is available.
|
||||
For the remote station, KST4Contest currently uses a fixed antenna height of 10 metres above the local terrain. Its transmit power and antenna gain come from the two **DX OM** fields. These values are deliberately assumptions: ON4KST provides neither the actual antenna height nor the complete station data of the remote operator.
|
||||
|
||||
Antenna gains must be entered in dBi. If a value is specified in dBd, it can be converted approximately as follows:
|
||||
Antenna gains must be entered in `dBi`. Add `2.15 dB` before entering a value specified in `dBd`:
|
||||
|
||||
`dBi = dBd + 2.15`
|
||||
```text
|
||||
dBi = dBd + 2.15 dB
|
||||
```
|
||||
|
||||
The current antenna direction, configured beamwidth, maximum QRB and bands enabled for the local station also affect the map display or its assessment. The current QTF and beamwidth, for example, determine the displayed antenna sector and the highlighting of stations inside that sector.
|
||||
The current QTF, configured antenna beamwidth and default maximum QRB affect the map display. The bands enabled for the local station and the frequency derived for the remote station also affect the selection of the analysis frequency.
|
||||
|
||||
The path analysis uses a fixed effective Earth-radius factor of `k = 4/3`. This is a common approximation for average tropospheric refraction. Actual propagation conditions can differ considerably.
|
||||
For the terrain profile, KST4Contest uses the antenna heights, elevation model and Earth curvature with a fixed effective Earth-radius factor of `k = 4/3`. The link budget additionally includes:
|
||||
|
||||
The link budget takes into account:
|
||||
|
||||
- the distance between both stations,
|
||||
- the analysis frequency,
|
||||
- the configured transmit powers,
|
||||
- the antenna gains,
|
||||
- estimated feeder losses,
|
||||
- distance and the current analysis frequency,
|
||||
- transmit powers and antenna gains in both directions,
|
||||
- frequency-dependent estimated feeder losses,
|
||||
- free-space path loss, and
|
||||
- a rough additional loss caused by obstructions in the terrain profile.
|
||||
- a rough additional loss caused by the relevant obstruction in the terrain profile.
|
||||
|
||||
The resulting received power and SSB or CW margins are technical estimates. They help to classify a possible path, but they are not a complete field-strength prediction. Current weather conditions, local obstructions, multipath propagation and unknown station parameters cannot be represented completely.
|
||||
The calculation is performed in both directions. The weaker direction determines the common SSB or CW margin. Optimistic power or antenna values therefore improve the displayed result, but they do not improve the real radio path.
|
||||
|
||||
The results remain engineering estimates. Current propagation conditions, local obstructions, vegetation, buildings, interference and unknown station parameters can change the actual result considerably. Operation, frequency selection and the limits of the calculation are described under [Station Map and Path Analysis](en-Features#station-map-and-path-analysis-from-v141).
|
||||
|
||||
---
|
||||
|
||||
## Server Settings (from v1.31)
|
||||
|
||||
The chat server DNS and port are configurable in the Preferences:
|
||||
The connection details for the ON4KST chat server are shown at the top of the **Station** tab. They normally do not need to be changed.
|
||||
|
||||
- **Server DNS**: Default `www.on4kst.org` (changed from `www.on4kst.info` in v1.31 hotfix).
|
||||
- **Port**: Default port of the ON4KST server.
|
||||
| Setting | Meaning |
|
||||
|---|---|
|
||||
| **ON4KST server** | DNS name or IP address of the chat server; the software default is `www.on4kst.org` |
|
||||
| **Port** | TCP port of the chat server; the default is `23001` |
|
||||
|
||||
A change is only needed if the server moves or an alternative endpoint is used.
|
||||
`www.on4kst.info` may also remain in an existing, working configuration. There is no reason to change it merely because the displayed name differs from the software default, provided that the chat connection remains reliable.
|
||||
|
||||
These values apply only to the outgoing ON4KST chat connection. They do not change the local DX cluster server or any connection to AirScout, PSTRotator or logging software.
|
||||
|
||||
The port must be between `1` and `65535`. An invalid entry is rejected and replaced with the last valid value. An incorrect server name or port, on the other hand, prevents KST4Contest from connecting to the chat.
|
||||
|
||||
Changing either value does not alter an existing TCP connection. Disconnect and reconnect the chat, or restart KST4Contest. Then use **Save Settings** so that the values are retained for the next program start.
|
||||
|
||||
---
|
||||
|
||||
## Log Sync Settings
|
||||
|
||||
Three methods are available for automatically marking worked stations. Details: [Log Synchronisation](en-Log-Sync).
|
||||
The **Log sync** tab selects the sources from which KST4Contest imports worked stations. The three input paths provide different levels of detail:
|
||||
|
||||
### Universal File Based Callsign Interpreter (Simplelogfile)
|
||||

|
||||
|
||||
Interprets any log file using regex for callsign patterns. No band information is available. Suitable as a fallback or for log programs that are not directly supported.
|
||||
| Input path | Data used | Result in KST4Contest |
|
||||
|---|---|---|
|
||||
| **Simplelogfile** | Callsigns read from a selected file | global Worked status, but no band or locator information |
|
||||
| **General QSO UDP listener** | QSO packets from UCXLog, QARTest, N1MM+ and DXLog.net | global and per-band Worked status plus worked grid square where both band and locator are transmitted |
|
||||
| **Win-Test network listener** | native Win-Test network packets | global and per-band Worked status, locator information and, depending on the settings, QRG synchronisation and sked handover |
|
||||
|
||||
### Network Listener for Logger's QSO UDP Broadcast
|
||||
The file-based interpreter reads the selected text file once per minute using a fixed callsign pattern. Matches are normalised to base callsigns and set only the global Worked status for all active variants. It is mainly useful when the logging application provides no supported network interface. A callsign match alone contains neither a reliable band nor a locator, so use one of the network listeners wherever possible if per-band information is required.
|
||||
|
||||
**Recommended method.** KST4Contest listens for UDP packets sent by the logging software to the broadcast address when a QSO is saved. Stations are marked with band information. UDP port: default **12060**. (Used by UCXLog, N1MM+, QARTest, DXLog.net, etc.).
|
||||
If the selected file does not exist, KST4Contest creates it and displays its full path together with checks for initial setup and the next contest. The file itself is the durable source; Worked marks derived from it are not stored in SQLite and are not reset automatically for a new contest.
|
||||
|
||||
### Win-Test Network Listener (Additional UDP Listener)
|
||||
The general QSO UDP listener is the recommended interface for UCXLog, QARTest, N1MM+ and DXLog.net. QSO and `RadioInfo` packets use the same configurable UDP port; the default is `12060`. Separate options in **Log sync** and **TRX sync** determine whether the received QSO and frequency information is processed.
|
||||
|
||||
A dedicated network listener for Win-Test. KST4Contest receives and processes Win-Test-specific UDP packets (including sked handovers) on the configured port.
|
||||
Win-Test uses its own network protocol and therefore has a separate listener. Its default port is `9871`. If this port is changed while the listener is enabled, KST4Contest restarts the Win-Test listener on the new port. After changing the shared UDP port `12060`, KST4Contest must instead be restarted completely.
|
||||
|
||||
All enabled input paths may be used in parallel and identical reports do not create separate Worked states. Network-derived Worked information is stored in the internal database. Simplelogfile marks remain runtime state derived from the selected file. Before using Simplelogfile, check whether the logging application already provides one of the supported network interfaces. KST4Contest must be running when a network QSO is transmitted unless the logging application can resend the existing log.
|
||||
|
||||
Configuration of the individual logging applications, band and locator handling, and the Win-Test sked handover are described under [Log Synchronisation](en-Log-Sync).
|
||||
|
||||
---
|
||||
|
||||
## TRX Sync Settings
|
||||
|
||||
Receives the current transceiver frequency from the logging software via UDP. This enables the automatic population of the `MYQRG` variable. Useful for:
|
||||
TRX synchronisation imports the current frequency from the logging application and makes it available in KST4Contest as the local QRG of the first chat category. QSO and frequency synchronisation may use the same UDP receiver, but they remain separate functions: receiving a `RadioInfo` packet does not mark a station as worked, and receiving a QSO packet does not automatically change the local QRG.
|
||||
|
||||
- Quickly inserting your own QRG into chat messages.
|
||||
- Automatic CQ beacon with current frequency.
|
||||

|
||||
|
||||
> **Note for multi-setup**: When running two logging programs on two computers but only one KST4Contest instance, only one logging program should send frequency packets. KST4Contest cannot distinguish between sources.
|
||||
### Available QRG Sources
|
||||
|
||||
| Source | Setting | Behaviour |
|
||||
|---|---|---|
|
||||
| **General RadioInfo listener** | `Update MYQRG from RadioInfo messages received on the shared log-sync port` | Processes compatible `RadioInfo` packets on the UDP port shared with QSO synchronisation. The default port is `12060`. |
|
||||
| **Win-Test STATUS** | `Win-Test STATUS QRG Sync` | Processes the main or pass frequency from native Win-Test `STATUS` packets. The Win-Test listener uses its separately configured port, which defaults to `9871`. |
|
||||
| **Manual entry** | Disable both automatic QRG sources | The local QRG can be entered manually in the main window. |
|
||||
|
||||
The general listener is intended for logging applications which transmit compatible `RadioInfo` packets. Depending on their individual configuration, this includes UCXLog, N1MM+, QARTest and DXLog.net. QSO and `RadioInfo` packets use the same port configured under **Log sync**, but separate options determine whether KST4Contest processes QSO information, TRX information or both packet types. An automatic QRG source must be enabled and actually supply valid packets; enabling it alone does not update `MYQRG`.
|
||||
|
||||
Restart KST4Contest after changing the shared UDP port. Changes to the two QRG-sync checkboxes take effect immediately.
|
||||
|
||||
### Which QRG Is Updated?
|
||||
|
||||
Both automatic sources update `MYQRG` only. This is the local QRG of the first or primary chat category.
|
||||
|
||||
If a second chat is enabled, its QRG remains independent. It is not derived from incoming TRX packets and is available through `SECONDQRG`. The first category can therefore follow the logging application's frequency automatically while a separate QRG is entered manually for the second category.
|
||||
|
||||
When at least one automatic QRG source is enabled and supplies a valid value, the first category's QRG field in the main window follows the received frequency. If the expected packets do not arrive, verify the interface before the contest. Manual entry is available when both the general RadioInfo listener and Win-Test STATUS synchronisation are disabled.
|
||||
|
||||
### Main or Pass Frequency from Win-Test
|
||||
|
||||
By default, KST4Contest uses the main frequency contained in the Win-Test `STATUS` packet.
|
||||
|
||||
Enable `Use pass frequency from Win-Test STATUS` to use the packet's pass frequency instead. This is useful, for example, when Win-Test maintains a different frequency during split operation and that is the frequency which should be announced in the chat.
|
||||
|
||||
If the packet does not contain a valid pass frequency, KST4Contest automatically falls back to the main frequency. A missing pass frequency therefore neither clears `MYQRG` nor replaces it with an obviously incorrect number.
|
||||
|
||||
Frequencies use a consistent KST4Contest display format, for example:
|
||||
|
||||
```text
|
||||
50.300.00
|
||||
144.300.00
|
||||
1296.100.00
|
||||
10368.100.00
|
||||
```
|
||||
|
||||
The number of digits before the first dot is derived from the frequency. Microwave frequencies with four or five MHz digits are therefore formatted correctly as well.
|
||||
|
||||
### Selecting the Win-Test Station
|
||||
|
||||
Several stations in a Win-Test network may transmit `STATUS` packets at the same time. `Win-Test station name filter` selects the station which is allowed to update the local QRG in KST4Contest.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
STN1
|
||||
```
|
||||
|
||||
The comparison is case-insensitive. If the field is empty, `STATUS` packets from every Win-Test station are accepted.
|
||||
|
||||
In a multi-operator setup, set the filter to the station name of the operating position which actually belongs to this KST4Contest instance. Otherwise, a packet from another position may replace the QRG currently being displayed.
|
||||
|
||||
### Using MYQRG and SECONDQRG
|
||||
|
||||
The synchronised QRG can be used in every text processed by the common KST4Contest variable resolver. This includes:
|
||||
|
||||
- the send field,
|
||||
- shortcuts,
|
||||
- snippets, and
|
||||
- automatic beacons.
|
||||
|
||||
`MYQRG` contains the complete QRG of the first chat category. `MYQRGSHORT` uses only its first seven characters. `SECONDQRG` contains the separately entered QRG of the second chat category.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
I am calling cq at MYQRG
|
||||
cq on MYQRGSHORT
|
||||
second chat qrg SECONDQRG
|
||||
```
|
||||
|
||||
The values are inserted when the message text is resolved. If the logging application changes frequency between two beacon runs, the next message already uses the updated value.
|
||||
|
||||
The local QRG may also be used as a fallback when handing a sked over to Win-Test. This only happens if the QRG can be parsed and belongs to the band explicitly selected for the sked. See [Log Synchronisation](en-Log-Sync#handing-skeds-over-to-win-test) for details.
|
||||
|
||||
Further information about text variables: [Macros and Variables](en-Macros-and-Variables#variables).
|
||||
|
||||
### Multiple Loggers or Radios
|
||||
|
||||
Every enabled QRG source writes to the same `MYQRG` value. KST4Contest does not currently assign incoming `RadioInfo` or `STATUS` packets to a particular radio or chat category.
|
||||
|
||||
If the general RadioInfo listener and Win-Test synchronisation are enabled at the same time, the most recently processed packet therefore determines the displayed QRG. The same applies when several logging applications send frequency packets to one KST4Contest instance.
|
||||
|
||||
For a setup containing several radios:
|
||||
|
||||
- QSO packets may be received from several logging applications.
|
||||
- Frequency packets should only be transmitted by the source which is intended to control `MYQRG`.
|
||||
- A Win-Test network should additionally use the station-name filter.
|
||||
- If two completely independent QRG synchronisations are required, two separate KST4Contest instances provide the clearer arrangement.
|
||||
|
||||
In other words, combining several Worked sources is useful. Combining several simultaneously transmitting frequency sources merely creates a contest over which packet arrived last.
|
||||
|
||||
Click **Save Settings** after completing the configuration.
|
||||
|
||||
---
|
||||
|
||||
## AirScout Settings
|
||||
|
||||
Configuration of the interface to AirScout for aircraft scatter detection. Details: [AirScout Integration](en-AirScout-Integration).
|
||||
The **AirScout** tab configures the UDP connection between KST4Contest and AirScout. KST4Contest does not request a general aircraft feed. It submits the station paths which are currently relevant, while AirScout calculates the matching aircraft and returns the result to the requesting KST4Contest instance.
|
||||
|
||||
AirScout `0.9.9.5` or newer is required.
|
||||
|
||||
{ width=85% }
|
||||
|
||||
### UDP Connection Settings
|
||||
|
||||
| Setting | Default | Use |
|
||||
|---|---:|---|
|
||||
| **Enable AirScout UDP integration** | disabled | Enables AirScout requests and the processing of returned information |
|
||||
| **AirScout server identifier** | `AS` | Logical name of the AirScout instance being addressed |
|
||||
| **KST4Contest client identifier** | `KST` | Logical name of this KST4Contest instance |
|
||||
| **AirScout UDP port** | `9872` | Shared UDP port for requests and responses |
|
||||
| **Select AirScout frequency automatically per station** | enabled | Derives a suitable band and frequency from the current context of each remote station |
|
||||
| **Forced AirScout band value** | `1440000` | Uses one fixed AirScout band value for every station when automatic selection is disabled |
|
||||
|
||||
When **Enable AirScout UDP integration** is disabled, KST4Contest neither sends AirScout requests nor processes incoming AirScout responses. The UDP receiver may remain bound so that the integration can be enabled again during the current connection.
|
||||
|
||||
KST4Contest sends AirScout packets to the broadcast address `255.255.255.255`. No separate destination IP address is therefore configured. AirScout and KST4Contest must be able to receive the same UDP broadcast; routers do not normally forward this type of broadcast into another network. If communication fails, check the UDP port, local firewall and network assignment first.
|
||||
|
||||
### Automatic Band Selection per Station
|
||||
|
||||
**Auto per station** is the recommended setting. Instead of using one fixed band value for every remote station, KST4Contest derives a suitable frequency from the available operating context.
|
||||
|
||||
The sources are evaluated in this order:
|
||||
|
||||
1. the most recently detected QRG of the remote station, provided that it is no more than 30 minutes old,
|
||||
2. one unambiguous complete QRG in the name field of an active chat entry,
|
||||
3. unambiguous band designators in the name field,
|
||||
4. 432 MHz if the same station is active in both the VHF/UHF and Microwave categories and 432 MHz is enabled for the local station,
|
||||
5. the lowest locally enabled band belonging to the supported chat category.
|
||||
|
||||
Active chat variants of the same base callsign are evaluated together. Entries such as `CALLSIGN`, `CALLSIGN-2` and `CALLSIGN-432` can therefore contribute to the same band decision while remaining separate chat members for message processing.
|
||||
|
||||
Only bands enabled under **My station uses …** are eligible for automatic selection. A manually assigned NOT-QRV mark excludes the corresponding band and takes precedence over automatically detected QRG or name information.
|
||||
|
||||
The 50/70 MHz, VHF/UHF, Microwave and EME/JT65 chat categories are supported. Other ON4KST categories do not participate in AirScout band resolution. If no sufficiently reliable frequency can be determined, KST4Contest omits the request for that station. An arbitrary fallback to 144 MHz would produce a syntactically complete packet, but not necessarily a useful calculation.
|
||||
|
||||
Automatic AirScout selection uses the same propagation-frequency resolver as the internal path analysis. Both functions therefore evaluate the station path from the same technical basis.
|
||||
|
||||
### Fixed AirScout Band
|
||||
|
||||
When **Auto per station** is disabled, KST4Contest uses the value entered under **Forced AirScout band value** for every station.
|
||||
|
||||
Enter the value in the unit used by the AirScout UDP interface:
|
||||
|
||||
| Band | AirScout value |
|
||||
|---|---:|
|
||||
| 50 MHz | `500000` |
|
||||
| 70 MHz | `700000` |
|
||||
| 144 MHz | `1440000` |
|
||||
| 432 MHz | `4320000` |
|
||||
| 1296 MHz | `12960000` |
|
||||
| 2320 MHz | `23200000` |
|
||||
| 3400 MHz | `34000000` |
|
||||
| 5760 MHz | `57600000` |
|
||||
| 10368 MHz | `103680000` |
|
||||
| 24048 MHz | `240480000` |
|
||||
|
||||
In fixed mode, neither the remote station's recently detected QRG nor a band stated in its name is considered. This option is therefore mainly useful for a station setup which is clearly limited to one band, or for troubleshooting.
|
||||
|
||||
### Server and Client Identifiers
|
||||
|
||||
The identifiers belong to the AirScout protocol. They are not DNS names or IP addresses.
|
||||
|
||||
Outgoing requests contain the client identifier followed by the server identifier:
|
||||
|
||||
```text
|
||||
"KST" "AS"
|
||||
```
|
||||
|
||||
AirScout returns them in the opposite order:
|
||||
|
||||
```text
|
||||
"AS" "KST"
|
||||
```
|
||||
|
||||
KST4Contest processes a response only if both identifiers exactly match the current configuration. The comparison is case-sensitive.
|
||||
|
||||
Identifiers must not be empty and must not contain quotation marks or line breaks.
|
||||
|
||||
If several KST4Contest instances operate in the same network, assign a distinct client identifier to each one, for example:
|
||||
|
||||
```text
|
||||
KST-144
|
||||
KST-432
|
||||
```
|
||||
|
||||
If several AirScout instances are present, use distinct server identifiers as well. This prevents a response intended for one operating position from being processed by another KST4Contest instance.
|
||||
|
||||
### Which Stations Are Requested?
|
||||
|
||||
KST4Contest starts the first periodical AirScout request approximately ten seconds after the chat connection has been established. Further requests follow every 60 seconds.
|
||||
|
||||
An active station is included only if:
|
||||
|
||||
- a usable callsign is available,
|
||||
- a locator is available,
|
||||
- its distance has been calculated,
|
||||
- its distance is below the configured **Maximum QRB**, and
|
||||
- a usable band can be determined.
|
||||
|
||||
Several active chat entries of the same base callsign do not create a separate identical path calculation for every suffix. The returned AirScout information is subsequently assigned to the corresponding active chat variants.
|
||||
|
||||
This selection does more than reduce network traffic. It also prevents AirScout from continuously calculating paths outside the intended working range of the local station.
|
||||
|
||||
### Applying Changed Settings
|
||||
|
||||
The following changes are used for new packets immediately after leaving the input field or changing the checkbox:
|
||||
|
||||
- enabling or disabling the AirScout integration,
|
||||
- server identifier,
|
||||
- client identifier,
|
||||
- automatic or fixed band selection, and
|
||||
- the forced band value.
|
||||
|
||||
After changing the UDP port, disconnect and reconnect the chat or restart KST4Contest. The existing UDP receiver otherwise remains bound to the previous port.
|
||||
|
||||
Click **Save Settings** afterwards to retain the configuration.
|
||||
|
||||
AirScout setup, aircraft display and the meaning of the returned AP information are described under [AirScout Integration](en-AirScout-Integration).
|
||||
|
||||
---
|
||||
|
||||
@@ -134,11 +376,15 @@ Configuration of the interface to AirScout for aircraft scatter detection. Detai
|
||||
|
||||

|
||||
|
||||
Three notification types are available:
|
||||
The three audio functions work independently:
|
||||
|
||||
1. **Simple sounds**: TADA sound for incoming messages, tick for sked direction detection, etc.
|
||||
2. **CW announcement**: The callsign of a station sending a private message is output as a CW signal.
|
||||
3. **Phonetic announcement**: The callsign is pronounced phonetically.
|
||||
1. **Simple sounds**: Plays short cues for new private messages, detected directional opportunities, sked reminders and `BAND+` hints.
|
||||
2. **CW announcement**: Spells the sender's callsign in CW for a new private message.
|
||||
3. **Phonetic announcement**: Speaks the sender's callsign phonetically for a new private message.
|
||||
|
||||
Each function can be enabled separately. CW and phonetic output can also be active at the same time.
|
||||
|
||||
PM-related audio output is triggered only by messages actually directed to the local login callsign. Messages shown additionally through PM Catching and messages added through QSO Monitoring remain silent.
|
||||
|
||||
### Fallback Band for Relative QRG Detection
|
||||
|
||||
@@ -163,7 +409,46 @@ The dropdown is neither a filter nor an override for complete frequencies. `432.
|
||||
|
||||
Before using the fallback, KST4Contest checks the sender's recent band context. If a complete frequency has been detected for the same station during the previous 30 minutes, that band takes precedence. A fallback setting of `144 MHz` therefore still turns `.100` into `432.100 MHz` if the station mentioned `432.088` shortly before.
|
||||
|
||||
Although the setting is located in the Notification tab, it affects the general QRG parser. It therefore influences the QRG column, detected active bands, priority calculations, band-upgrade hints and other functions which use a known station frequency – not only DX cluster spots.
|
||||
Although this setting is located in the Notification tab, it affects the general QRG parser. It therefore influences the QRG column, detected active bands, priority calculations, band-upgrade hints and other functions which use a known station frequency – not only DX cluster spots.
|
||||
|
||||
Further details, including numbers which are deliberately ignored, are described under [QRG Detection](en-Features#qrg-detection).
|
||||
|
||||
### Local DX Cluster Output
|
||||
|
||||
KST4Contest can forward detected directional opportunities to logging software as DX cluster spots. A frequency recognised in the chat can therefore appear directly in the logger's band map without being entered manually.
|
||||
|
||||
The **Enable the local DX Cluster server …** checkbox starts or stops the local TCP server. When KST4Contest is connected to the chat, the change takes effect immediately.
|
||||
|
||||
The following settings and controls belong to the local DX cluster output:
|
||||
|
||||
- **TCP port**: Port on which KST4Contest accepts connections from DX cluster clients. The default is `8000`. Changing the port while the server is running restarts it on the new port. The logger must then reconnect to that port.
|
||||
- **Fallback band for relative QRG detection**: The global fallback band described above. The test spot uses `.300` on this band. Actual spots use the QRG detected for the respective sender.
|
||||
- **Spotter callsign**: Callsign shown as the spotter in generated DX cluster entries. A callsign different from the contest callsign should be used. Some logging programs filter spots apparently sent by the local station or treat them differently from external spots.
|
||||
- **Send test spot**: Sends the following entry to every currently connected DX cluster client:
|
||||
|
||||
```text
|
||||
Spotted callsign: DO5AMF
|
||||
Comment: DXC test: You donated $100!
|
||||
Frequency: .300 on the selected fallback band
|
||||
```
|
||||
|
||||
With `144 MHz` selected as the fallback band, the resulting frequency is approximately `144.300 MHz`.
|
||||
|
||||
All spots use a fixed, DXSpider-compatible 75-character payload line with a 30-character comment field. Longer comments are deliberately truncated at this protocol boundary; the DX callsign is not. A callsign longer than twelve characters causes the affected spot to be rejected and logged.
|
||||
|
||||
The comment is a deliberately retained Easter egg. It has no technical meaning and, despite being remarkably specific, does not initiate a payment. Its practical purpose is to make the test spot easy to identify in the logging software.
|
||||
|
||||
The test works only if
|
||||
|
||||
1. KST4Contest is connected to the ON4KST chat,
|
||||
2. the local DX cluster server is enabled, and
|
||||
3. at least one DX cluster client is connected to KST4Contest.
|
||||
|
||||
KST4Contest does not generate a spot for every frequency found in the chat. An actual spot is created only when a directed message between two stations indicates a relevant antenna direction for the local station and a usable frequency is known for the sender.
|
||||
|
||||
The complete derivation and logger setup are described under [Built-in DX Cluster Server](en-DX-Cluster-Server).
|
||||
|
||||
---
|
||||
|
||||
### Band Upgrade Hint after a Log Entry
|
||||
|
||||
@@ -188,7 +473,7 @@ The two options serve different purposes:
|
||||
|
||||
The Priority Boost is only one factor in the complete calculation. Distance, antenna direction, recent activity, AirScout data, skeds and negative hints may still change the final order. Enabling the option therefore guarantees neither a particular score nor a particular position in the priority list.
|
||||
|
||||
The other score weights currently have no separate user-interface controls. Several existing settings nevertheless provide input data for the calculation, particularly the [enabled bands](#enabled-bands), [antenna beamwidth](#antenna-beamwidth), [default maximum QRB](#default-maximum-qrb) and [AirScout settings](#airscout-settings).
|
||||
The other score weights currently have no separate user-interface controls. Several existing settings nevertheless provide input data for the calculation, particularly the [active bands](#active-bands), [antenna beamwidth](#antenna-beamwidth), [default maximum QRB](#default-maximum-qrb) and [AirScout settings](#airscout-settings).
|
||||
|
||||
The complete calculation is described under [Priority Score and Priority List](en-Features#priority-score-and-priority-list-from-v140).
|
||||
|
||||
@@ -201,42 +486,261 @@ Further explanation: [Band Upgrade Hint after a Log Entry](en-Features#band-upgr
|
||||
|
||||
## Shortcut Settings
|
||||
|
||||
Configuration of quick-access buttons that appear directly in the main window. Clicking a button inserts the configured text into the send field. All [variables](Macros-and-Variables#variables) can be used.
|
||||

|
||||
|
||||
Each entry in the upper part of the **Shortcuts** tab creates one button above the message field in the main window. Pressing the button appends its configured text to the current contents of the send field.
|
||||
|
||||
If the shortcut contains a [variable](en-Macros-and-Variables#variables), it is replaced with its current value when the text is inserted. A shortcut such as
|
||||
|
||||
```text
|
||||
pse call me at MYQRGSHORT
|
||||
```
|
||||
|
||||
may therefore insert:
|
||||
|
||||
```text
|
||||
pse call me at 144.388
|
||||
```
|
||||
|
||||
The exact entries `MYQRG` and `SECONDQRG` are additionally highlighted as QRG buttons. They insert the current frequency of the primary or secondary chat category respectively.
|
||||
|
||||
The shortcut `/SETNAME MYQRG` is highlighted as well. When it is pressed, KST4Contest resolves `MYQRG` and inserts the complete server command into the send field. The command is not transmitted automatically and can still be checked before it is sent with `Enter` or **TX**.
|
||||
|
||||
|
||||
The order of the table determines the order of the buttons in the main window. Manage the entries as follows:
|
||||
|
||||
1. **Add shortcut** creates a new entry at the beginning of the list and immediately opens it for editing.
|
||||
2. Double-click an existing entry to edit it. Press `Enter` to accept the change.
|
||||
3. To remove an entry, delete its complete contents and confirm with `Enter`.
|
||||
4. Use **Move selected up** and **Move selected down** to change the position of the selected entry.
|
||||
|
||||
Changes appear in the main window immediately. Use **Save Settings** afterwards if they should remain available after the next program start.
|
||||
|
||||
---
|
||||
|
||||
## Snippet Settings
|
||||
|
||||
Text snippets are accessible via:
|
||||
Snippets are longer text blocks intended primarily for messages to a selected station. They can be opened through:
|
||||
|
||||
- **Right-click** on a callsign in the user list
|
||||
- **Right-click** in the CQ message table
|
||||
- **Right-click** in the PM message table
|
||||
- **Keyboard shortcuts**: `Ctrl+1` to `Ctrl+0` for the first 10 snippets
|
||||
- a right-click on a station in the user list;
|
||||
- a right-click on a message in the public chat table;
|
||||
- a right-click on a message in the PM table; or
|
||||
- `Ctrl+1` through `Ctrl+0` for the first ten entries in the snippet list.
|
||||
|
||||
If a callsign is selected in the user list, the snippet is addressed as a direct message:
|
||||
`/CQ CALLSIGN <snippet text>`
|
||||
The keyboard mapping follows the order of the table:
|
||||
|
||||
| Key combination | Snippet |
|
||||
|---|---:|
|
||||
| `Ctrl+1` | first entry |
|
||||
| `Ctrl+2` | second entry |
|
||||
| … | … |
|
||||
| `Ctrl+9` | ninth entry |
|
||||
| `Ctrl+0` | tenth entry |
|
||||
|
||||
A snippet selected from a context menu is appended to the message already prepared in the send field. Selecting a station or message will normally have inserted the appropriate `/cq` destination first.
|
||||
|
||||
A keyboard shortcut behaves differently: it replaces the current contents of the send field with a complete directed message:
|
||||
|
||||
```text
|
||||
/cq CALLSIGN snippet text
|
||||
```
|
||||
|
||||
The complete visible callsign, including any suffix, is retained. Selecting `9A0BB-70` may therefore produce:
|
||||
|
||||
```text
|
||||
/cq 9A0BB-70 pse ur qrg?
|
||||
```
|
||||
|
||||
The selected station's chat category is retained for transmission. If no station is selected, or no snippet is assigned to the chosen key combination, nothing is inserted.
|
||||
|
||||
Variables are resolved when the snippet is inserted. Station-specific variables such as `QRZNAME`, `FIRSTAP` and `SECONDAP` refer to the currently selected station. The prepared message is not sent automatically and can still be checked or edited. Press `Enter` or **TX** to send it; `Esc` clears the send field.
|
||||
|
||||
The snippet list is edited in the same way as the shortcut list:
|
||||
|
||||
1. **Add new snippet** creates a new entry at the beginning of the list.
|
||||
2. Double-click an existing entry to edit it.
|
||||
3. Press `Enter` to accept the change.
|
||||
4. Confirming an empty entry removes it.
|
||||
5. **Move selected up** and **Move selected down** change both the displayed order and the assignment to `Ctrl+1` through `Ctrl+0`.
|
||||
|
||||
The context menus and keyboard mappings are updated immediately. Use **Save Settings** afterwards to store the modified list permanently.
|
||||
|
||||
The complete list of available placeholders and their limitations is described under [Macros and Variables](en-Macros-and-Variables).
|
||||
|
||||
---
|
||||
|
||||
## Beacon Settings
|
||||
|
||||
Configuration of an automatic interval beacon in the public chat channel. Recommended: use the `MYQRG` variable in the text so the current frequency is always up to date. Interval and text are freely configurable.
|
||||

|
||||
|
||||
> **Tip**: Enable the beacon when calling CQ and quickly disable it in the settings window when not calling.
|
||||
A beacon sends a public CQ message at regular intervals. It is intended for operating situations in which the local station calls CQ on a fixed frequency for an extended period. Other stations receive current QRG information without requiring the operator to enter the same message repeatedly.
|
||||
|
||||
KST4Contest uses one shared timer for both chat categories. Each category nevertheless has its own enable setting and message template:
|
||||
|
||||
- **Enable CQ beacon** enables the beacon for the respective category.
|
||||
- **Beacon message** contains the public message for that category.
|
||||
- **Shared beacon interval** sets the common interval used by both categories.
|
||||
|
||||
When both beacons are enabled, they are sent one after the other in their respective categories during the same timer run. The second beacon is only considered while the second category is enabled and fully synchronised in the same ON4KST session.
|
||||
|
||||
### Interval and timer behaviour
|
||||
|
||||
The interval is entered in whole minutes. The minimum permitted value is one minute.
|
||||
|
||||
After the chat connection has been established, KST4Contest performs the first beacon check after approximately ten seconds. The configured interval applies after that initial check.
|
||||
|
||||
Changing the interval while connected restarts the countdown with the new value. The change itself does not cause an immediate beacon message.
|
||||
|
||||
Both categories use the same timer. Separate intervals for the primary and secondary chat cannot be configured.
|
||||
|
||||
### Message text and variables
|
||||
|
||||
A beacon may use the [global variables](en-Macros-and-Variables#variables-in-the-beacon) which depend only on the local station:
|
||||
|
||||
- `MYQRG`
|
||||
- `MYQRGSHORT`
|
||||
- `SECONDQRG`
|
||||
- `MYLOCATOR`
|
||||
- `MYLOCATORSHORT`
|
||||
- `MYCALL`
|
||||
- `MYQTF`
|
||||
|
||||
A suitable message for the primary chat category is:
|
||||
|
||||
```text
|
||||
calling cq at MYQRGSHORT, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Use `SECONDQRG` for the second chat if it operates on a different frequency:
|
||||
|
||||
```text
|
||||
calling cq at SECONDQRG, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Variables are resolved again on every timer run. If the logging software changes the QRG stored in `MYQRG`, the next beacon can already contain the updated value. The message template does not have to be edited.
|
||||
|
||||
`MYQRG` and `MYQRGSHORT` always refer to the primary chat category. Enabling or selecting the second chat does not change this assignment.
|
||||
|
||||
Station-specific variables such as `QRZNAME`, `FIRSTAP` and `SECONDAP` require a selected remote station. A public beacon has no such destination, so these variables are not resolved in beacon messages.
|
||||
|
||||
### Message validation
|
||||
|
||||
KST4Contest validates both the configured template and the message which remains after all variables have been resolved.
|
||||
|
||||
The following restrictions apply:
|
||||
|
||||
- The final message must not be empty.
|
||||
- It must not exceed 120 characters.
|
||||
- The protocol separator `|` is not permitted.
|
||||
- Line breaks are not permitted.
|
||||
|
||||
An invalid entry is not accepted as the new beacon configuration. If a template becomes invalid only after resolving its variables, for example because the resulting text exceeds 120 characters, that beacon run is skipped.
|
||||
|
||||
A template which consists only of a temporarily empty global variable may still be stored. This can happen during startup before the first QRG has been received from the logger. KST4Contest does not send an empty message while the variable has no usable value.
|
||||
|
||||
### When should the beacon be disabled?
|
||||
|
||||
The beacon is useful only while its QRG matches the actual operation. Leaving it enabled while searching the band or changing frequencies frequently may cause other stations to look for the local station on an obsolete frequency.
|
||||
|
||||
In plain terms: the beacon saves work while calling CQ on a fixed QRG. It should be disabled while moving around the band.
|
||||
|
||||
Changes take effect during the current connection. Use **Save Settings** afterwards to retain the enable settings, message templates and interval for the next program start.
|
||||
|
||||
---
|
||||
|
||||
## Messagehandling Settings (from v1.25)
|
||||
|
||||
New settings section with the following options:
|
||||

|
||||
|
||||
- **Auto-reply to all incoming messages**: Configurable automatic reply to private messages.
|
||||
- **Auto-reply with own CQ QRG**: When someone asks for your QRG, KST4Contest automatically replies with the content of the `MYQRG` variable.
|
||||
- **Default filter for the userinfo window**: Pre-configured message filter for the station info window *(for Gianluca :-) )*.
|
||||
The most important use of the general automatic reply concerns stations which are logged into the ON4KST chat but are not taking part in the current contest. During larger contests, sked requests are sometimes sent to many logged-in stations without first checking whether they are participating. Without an automatic reply, the recipients would have to enter the same refusal repeatedly.
|
||||
|
||||
KST4Contest can answer these requests with a predefined message. A separate function provides the local QRG when a private message contains a recognised frequency request. Both functions can be enabled independently.
|
||||
|
||||
### General automatic reply
|
||||
|
||||
**Enable automatic reply to all private messages** answers incoming private messages with the text entered in the adjacent field. One common text is used for both chat categories. The configured capitalisation is preserved.
|
||||
|
||||
A suitable message is:
|
||||
|
||||
```text
|
||||
Sri, I am not taking part in this contest. No skeds.
|
||||
```
|
||||
|
||||
Do not add the `[KST4C Automsg]` prefix to the configured text. KST4Contest inserts it automatically.
|
||||
|
||||
The message received by the remote station may therefore be:
|
||||
|
||||
```text
|
||||
[KST4C Automsg] Sri, I am not taking part in this contest. No skeds.
|
||||
```
|
||||
|
||||
The incoming private message remains visible. The function neither blocks nor discards the request; it merely avoids entering the same answer repeatedly.
|
||||
|
||||
The reply is addressed to the sender's complete callsign, including any visible suffix, and sent through the chat category in which the private message was received. This distinction matters when two categories are connected at the same time: a request received through the microwave chat must not be answered accidentally through the VHF/UHF chat.
|
||||
|
||||
An empty or whitespace-only answer does not produce an automatic message. A configured text containing the protocol separator `|` or a line break is rejected as well.
|
||||
|
||||
### Automatic QRG reply
|
||||
|
||||
**Enable automatic QRG replies** reacts to common QRG requests. Matching is case-insensitive and looks for the following text fragments:
|
||||
|
||||
```text
|
||||
ur qrg?
|
||||
your qrg?
|
||||
qrg?
|
||||
freq?
|
||||
pse qrg
|
||||
```
|
||||
|
||||
The answer contains only the QRG belonging to the category in which the request was received:
|
||||
|
||||
| Incoming private message | QRG used for the reply |
|
||||
|---|---|
|
||||
| Primary category | current QRG of the primary category |
|
||||
| Second chat category | current QRG of the second category |
|
||||
|
||||
A possible reply is:
|
||||
|
||||
```text
|
||||
[KST4C Automsg] QRG is: 144.300.00
|
||||
```
|
||||
|
||||
The values come from the same QRG fields used by `MYQRG` and `SECONDQRG`. The primary QRG may be entered manually or updated through [TRX synchronisation](#trx-sync-settings). The second category uses the value configured or entered for that category.
|
||||
|
||||
If no QRG is available for the incoming category, KST4Contest does not send an incomplete reply. A message containing `QRG is:` without a frequency would technically answer the request while providing no useful information. It is therefore rejected before reaching the transmit queue.
|
||||
|
||||
When both automatic-reply functions are enabled, the QRG reply takes precedence. A recognised QRG request does not additionally produce the general reply. If the required QRG is missing, KST4Contest does not fall back to the general answer.
|
||||
|
||||
### Protection against repeated replies
|
||||
|
||||
Every automatically generated reply contains the fixed prefix:
|
||||
|
||||
```text
|
||||
[KST4C Automsg]
|
||||
```
|
||||
|
||||
The general and QRG-specific functions ignore messages which already contain this prefix. This prevents two clients with automatic replies enabled from answering each other indefinitely.
|
||||
|
||||
A common two-minute cooldown additionally applies to both reply types. The cooldown is tracked separately for each complete callsign and chat category.
|
||||
|
||||
This means:
|
||||
|
||||
- `CALLSIGN-2` and `CALLSIGN-70` have separate cooldowns.
|
||||
- The same complete callsign can still receive an independent reply in another chat category.
|
||||
- A general reply also suppresses a QRG reply to the same callsign in the same category for two minutes.
|
||||
- A QRG reply likewise suppresses the general reply.
|
||||
|
||||
The cooldown starts only after KST4Contest has produced a complete, locally valid message and placed it in the transmit queue. A missing QRG, an empty general reply or a message rejected because of invalid characters does not start the cooldown. Once the missing information has been corrected, a valid reply can therefore be generated immediately.
|
||||
|
||||
> **Note**: The configured text should describe the actual operating status clearly. If the station is only observing the contest and does not accept skeds, say exactly that. A vague automatic message is likely to produce another question – which is precisely the work this function is intended to avoid.
|
||||
|
||||
Changes take effect during the current connection. Use **Save Settings** afterwards to retain the enable settings and general reply text for the next program start.
|
||||
|
||||
Further background: [Automatic Replies to Private Messages](en-Features#automatic-replies-to-private-messages-from-v125).
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Win-Test Network Listener (from v1.31)
|
||||
|
||||
The Win-Test network listener processes the native Win-Test UDP protocol. It is independent of the general QSO UDP listener on port `12060` and has three separate tasks:
|
||||
@@ -280,27 +784,118 @@ Data handling and QRG selection: [Log Synchronisation – Win-Test](en-Log-Sync#
|
||||
|
||||
---
|
||||
|
||||
## PSTRotator Settings (from v1.31)
|
||||
## PSTRotator Settings (from v1.31, fully configurable from v1.40)
|
||||
|
||||
KST4Contest can control antenna direction via PSTRotator.
|
||||
KST4Contest can set an antenna direction through the PSTRotator UDP interface and use the current position reported by PSTRotator as the local QTF.
|
||||
|
||||
Settings:
|
||||
- **Enable/Disable**: Checkbox in Preferences (from v1.40).
|
||||
- **IP address**: IP address of the PSTRotator computer (default: `127.0.0.1` when running on the same PC).
|
||||
- **Port**: Communication port of PSTRotator.
|
||||
The settings are located in the **Station** tab:
|
||||
|
||||
> **Note**: After clicking a direction button, KST4Contest waits briefly for the rotator response. With slow rotors (e.g. SPID) there may be a small delay.
|
||||
| Setting | Default | Purpose |
|
||||
|---|---:|---|
|
||||
| **Enable PSTRotator** | disabled | Starts UDP communication with PSTRotator |
|
||||
| **PSTRotator host** | `127.0.0.1` | Hostname or IP address of the computer running PSTRotator |
|
||||
| **PSTRotator UDP port** | `12000` | UDP port on which PSTRotator receives control commands |
|
||||
|
||||
When both applications run on the same computer, `127.0.0.1` is normally the clearest setting. If PSTRotator runs on another computer in the station network, enter its reachable IP address or DNS name.
|
||||
|
||||
The port must be between `1` and `65534`. Port `65535` cannot be used because PSTRotator reports its position on the following port.
|
||||
|
||||
### Preparing PSTRotator
|
||||
|
||||
Configure the same UDP port under **Communication → UDP Control Port** in PSTRotator and enable **UDP Control**.
|
||||
|
||||
The default configuration uses the following pair:
|
||||
|
||||
| Direction | UDP port |
|
||||
|---|---:|
|
||||
| KST4Contest → PSTRotator | `12000` |
|
||||
| PSTRotator → KST4Contest | `12001` |
|
||||
|
||||
KST4Contest binds the return port automatically. It is not configured separately.
|
||||
|
||||
When the programs run on different computers, the local firewalls and station network must permit UDP traffic in both directions. KST4Contest cannot receive position reports if another program already occupies the return port.
|
||||
|
||||
The complete UDP protocol is documented in the [PSTRotatorAz User Manual](https://www.qsl.net/yo3dmu/ANT/PstRotatorAz%20User%20Manual.pdf).
|
||||
|
||||
### Updating the local QTF
|
||||
|
||||
KST4Contest asks PSTRotator for the current azimuth and operating mode every two seconds. The reported azimuth becomes `actualQTF`.
|
||||
|
||||
While PSTRotator integration is enabled, the QTF field in the main window is therefore read-only. It displays the most recent position reported by PSTRotator.
|
||||
|
||||
This QTF is used by:
|
||||
|
||||
- the direction filter;
|
||||
- the derivation of direction opportunities;
|
||||
- the Priority Score;
|
||||
- the antenna sector on the station map;
|
||||
- the AP and sked timeline; and
|
||||
- the `MYQTF` variable.
|
||||
|
||||
A received rotator position is therefore more than a displayed value. It changes several functions which depend on the current antenna direction.
|
||||
|
||||
The current integration uses azimuth only. Elevation control and complete azimuth/elevation tracking are outside its present scope.
|
||||
|
||||
### Applying changed settings
|
||||
|
||||
The enable setting, host and port are evaluated when the rotator connection is started. After changing them, disconnect and reconnect the ON4KST session or restart KST4Contest.
|
||||
|
||||
Use **Save Settings** afterwards so that the values are restored at the next program start.
|
||||
|
||||
---
|
||||
|
||||
## Sniffer Settings (from v1.31)
|
||||
|
||||
The QSO sniffer filters chat messages from configurable callsigns and forwards them to the PM window.
|
||||
QSO monitoring is intended for stations whose communication should remain visible during busy chat activity. This may be a rare station, a DXpedition or another station in the same contest team whose sked arrangements should not disappear in the general message traffic.
|
||||
|
||||
Settings:
|
||||
- **Callsign list**: Comma-separated list of callsigns whose messages are always forwarded to the PM window.
|
||||
The callsign list is maintained under **QSO monitoring** in the **Notification** tab.
|
||||
|
||||
Use case: Keep track of important stations (e.g. DX expeditions or trusted contest allies) without constantly monitoring the main chat.
|
||||
For every monitored base callsign, KST4Contest additionally shows messages in the PM table when a variant of that callsign is either the sender or the receiver. Both connected chat categories are included.
|
||||
|
||||
The list intentionally uses the normalised base callsign. The following entries therefore produce the same monitoring entry:
|
||||
|
||||
```text
|
||||
DN9APW
|
||||
DN9APW-2
|
||||
DN9APW-70
|
||||
DN9APW-144
|
||||
```
|
||||
|
||||
In every case, KST4Contest stores and displays:
|
||||
|
||||
```text
|
||||
DN9APW
|
||||
```
|
||||
|
||||
The different KST suffixes of one station do not have to be entered separately. A later message sent by `DN9APW-2` or addressed to `DN9APW-70` is covered by the same entry.
|
||||
|
||||
This aggregation applies to QSO monitoring only. Active ChatMember objects, complete message destinations and chat categories remain separate. A message addressed to `DN9APW-70` is therefore not redirected to `DN9APW-2`.
|
||||
|
||||
A monitored message is marked in the PM table using the complete visible callsigns of its sender and receiver:
|
||||
|
||||
```text
|
||||
Sniffed: (DN9APW-2 > DL0ABC-70) Message text
|
||||
```
|
||||
|
||||
The original message remains in its normal table. Monitoring changes neither its contents nor its routing.
|
||||
|
||||
A message is included only when the monitored station is actually its sender or receiver. Merely mentioning the callsign in the message text is not sufficient. Public messages sent by a monitored station are included as well; their receiver is displayed as `ALL`.
|
||||
|
||||
A message which is already addressed directly to the local callsign remains a normal private message and does not receive an additional `Sniffed:` marker.
|
||||
|
||||
Manage the list as follows:
|
||||
|
||||
1. Press **Add monitored callsign** to add an entry.
|
||||
2. Double-click an existing entry to edit it and press `Enter` to apply the change.
|
||||
3. To remove an entry, delete the complete cell contents and press `Enter`.
|
||||
|
||||
The entered value may contain a visible KST suffix or portable components. KST4Contest normalises it to the base callsign before storing it. Different variants of the same base callsign are therefore treated as duplicates.
|
||||
|
||||
Changes to the list take effect immediately. Press **Save Settings** afterwards to retain them. The base callsigns are stored in `preferences.xml` and restored at the next program start.
|
||||
|
||||
> Base-callsign monitoring across KST suffixes is included from v1.42 onwards.
|
||||
|
||||
Further background and the distinction from message routing: [QSO Sniffer](en-Features#qso-sniffer-from-v131).
|
||||
|
||||
---
|
||||
|
||||
@@ -326,6 +921,8 @@ The internal SQLite database stores contest-related state independently of the l
|
||||
- manually assigned NOT-QRV marks per band, and
|
||||
- worked four-character grid squares per band.
|
||||
|
||||
Worked marks from the Simplelogfile interpreter are the exception. They are copied from the selected file into runtime state only and are not persisted in SQLite. The file is the durable source and has no automatic contest reset. A database reset does not change or empty it; callsigns contained in the file are marked as worked again during the next periodic evaluation.
|
||||
|
||||
The normalised callsign, without visible chat brackets or category formatting, is used as the key. This allows active variants of the same callsign to be evaluated consistently.
|
||||
|
||||
Worked and NOT-QRV information expires automatically three days after its most recent change. Stored grid squares expire three days after the corresponding log entry. A manual reset before every contest is therefore normally unnecessary.
|
||||
@@ -340,7 +937,7 @@ Display and derivation: [Worked Callsigns, New Bands and New Grid Squares](en-Fe
|
||||
|
||||
## Dark Mode (from v1.26)
|
||||
|
||||
Toggle via the menu: **Window → Use Dark Mode**. The colors can be individually customized via CSS.
|
||||
Enable Dark Mode through **Windows → Use dark mode design**. Use **Windows → Use default mode design** to restore the normal light colour scheme.
|
||||
|
||||
---
|
||||
|
||||
@@ -348,6 +945,6 @@ Toggle via the menu: **Window → Use Dark Mode**. The colors can be individuall
|
||||
|
||||
Click **"Save Settings"** after **every** change! Without saving, all changes will be lost on the next start.
|
||||
|
||||
- Storage location: `~/.praktikst/preferences.xml` on Linux and `%USERPROFILE%\.praktikst\preferences.xml` (or `C:\Users\<Username>\.praktikst\preferences.xml`) on Windows
|
||||
- Storage location: `~/.praktiKST/preferences.xml` on Linux and macOS and `%USERPROFILE%\.praktiKST\preferences.xml` (or `C:\Users\<Username>\.praktiKST\preferences.xml`) on Windows
|
||||
- From v1.21: Window sizes and divider positions are also saved.
|
||||
- If you encounter problems: delete the configuration file → KST4Contest will create a new one with default values.
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
# Contest Workflow with KST4Contest
|
||||
|
||||
> You are reading the English version | [Deutsche Version](de-Contest-Workflow)
|
||||
|
||||
KST4Contest brings chat activity, station selection, known QRGs, worked status, skeds, aircraft scatter timing and other station data together in one interface. Its value does not come from one individual indicator, but from the way this information works together during an active contest.
|
||||
|
||||
This page describes a complete operating workflow. Individual functions and their technical limitations remain documented under [Features](en-Features), [User Interface](en-User-Interface), [Log Synchronisation](en-Log-Sync) and [AirScout Integration](en-AirScout-Integration).
|
||||
|
||||
---
|
||||
|
||||
## Purpose and Limitations
|
||||
|
||||
KST4Contest is intended to reduce the time between recognising an opportunity and attempting the actual QSO.
|
||||
|
||||
The programme can show, among other things:
|
||||
|
||||
- which stations are active,
|
||||
- on which bands and QRGs they were most recently detected,
|
||||
- which stations have already been worked,
|
||||
- which additional bands may still be available,
|
||||
- which candidates match the current antenna direction,
|
||||
- when an aircraft scatter window is expected, and
|
||||
- which station is currently working in a direction useful to the local station.
|
||||
|
||||
These remain decision aids. A high priority score is not a QSO probability. An aircraft geometry rated at 100% by AirScout does not guarantee a contact either. The operator must still decide whether a QRG is actually clear, whether the remote station is listening and whether the path works under the current conditions.
|
||||
|
||||
---
|
||||
|
||||
## Before the Contest
|
||||
|
||||
The important settings should be checked before the first interesting sked appears.
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
Check at least:
|
||||
|
||||
- the local callsign, password and locator,
|
||||
- the primary chat category,
|
||||
- login and settings for the second category if it is used,
|
||||
- locally enabled bands,
|
||||
- antenna beamwidth,
|
||||
- maximum useful distance,
|
||||
- `MYQRG` and, where applicable, `SECONDQRG`,
|
||||
- log synchronisation, and
|
||||
- the required shortcuts, snippets and message variables.
|
||||
|
||||
Antenna beamwidth and maximum distance depend on the station. At DM5M, for example, the actual antenna system is represented by a beamwidth of 69° and a maximum distance of 900 km. These are not general recommended values.
|
||||
|
||||
Use **Save Settings** for permanent changes. After connecting, the `LINK` indicator should be green. Only then have login and user-list synchronisation been completed.
|
||||
|
||||
### Automatic Replies
|
||||
|
||||
The automatic QRG reply is part of the active contest workflow. It answers repeated QRG requests and removes some routine work from the chat operator.
|
||||
|
||||
This is different from the general automatic reply. The general reply can react to all incoming requests and is mainly useful while the station is temporarily not QRV or does not want to take part in sked operation. It avoids unnecessary follow-up work for both the local and requesting stations.
|
||||
|
||||
### Optional Connections
|
||||
|
||||
Enable only interfaces which are actually required and have already been tested:
|
||||
|
||||
- logging software or Simplelogfile,
|
||||
- TRX synchronisation,
|
||||
- AirScout,
|
||||
- PSTRotator,
|
||||
- Win-Test sked handover, and
|
||||
- the local DX Cluster server.
|
||||
|
||||
A contest is not an ideal time to investigate radio conditions and a newly enabled network interface at the same time.
|
||||
|
||||
Before each contest, verify that the logging application writes the current contest file to the path selected for the Simplelogfile. A test QSO should appear as globally worked within one minute. KST4Contest does not reset Worked marks derived from this file automatically when the contest changes.
|
||||
|
||||
---
|
||||
|
||||
## Basic Contest Cycle
|
||||
|
||||
The normal operating cycle repeats:
|
||||
|
||||
1. Call CQ or run an agreed sked.
|
||||
2. Monitor chat activity, the priority list, map and AP timeline.
|
||||
3. Select a suitable candidate.
|
||||
4. Decide between the local and remote QRG.
|
||||
5. Attempt the QSO.
|
||||
6. Log a successful contact immediately.
|
||||
7. Check for another band opportunity.
|
||||
8. Mark a meaningful failed attempt with **Sked fail**.
|
||||
9. Return to CQ operation or continue with the next candidate.
|
||||
|
||||
KST4Contest keeps the required information together between these steps. Changing frequency, calling, listening and making the actual decision deliberately remain operator tasks.
|
||||
|
||||
---
|
||||
|
||||
## CQ Operation
|
||||
|
||||
During operation on a mainly fixed CQ frequency, `MYQRG` and `SECONDQRG` should match the frequencies actually in use. TRX synchronisation can update `MYQRG` automatically only when it is enabled and actually supplies valid packets. Before the contest, verify this with a real frequency change. Without a working automatic source, the value must be maintained manually.
|
||||
|
||||
The beacon can publish the current QRG, locator and antenna direction in the chat at regular intervals. Its variables are evaluated again for every transmission.
|
||||
|
||||
Disable the beacon while scanning across several frequencies. An automatically published QRG is useful only while it is still correct.
|
||||
|
||||
Shortcuts and snippets should cover the messages required regularly, for example:
|
||||
|
||||
- asking a station to listen on the local QRG,
|
||||
- asking for the remote QRG,
|
||||
- announcing a move to the remote QRG,
|
||||
- confirming antenna direction, and
|
||||
- proposing a sked.
|
||||
|
||||
Further details are available under [Macros and Variables](en-Macros-and-Variables).
|
||||
|
||||
---
|
||||
|
||||
## Selecting Candidates
|
||||
|
||||
The user list can be reduced to the current operating situation by combining QTF, QRB, Worked, band, activity, New Bands, Tropo and AirScout filters.
|
||||
|
||||
The priority list and AP timeline add further context:
|
||||
|
||||
- The priority score combines several known criteria.
|
||||
- The AP timeline arranges skeds and expected aircraft scatter opportunities by time.
|
||||
- The station map shows geographical position, antenna direction and radio path.
|
||||
- Worked and band status help prevent unnecessary duplicate work.
|
||||
|
||||

|
||||
|
||||
The score is a sorting aid. Before attempting a contact, continue to check the callsign, category, band, QRG, direction, distance and age of the underlying information.
|
||||
|
||||
A deliberate selection in the user list, priority list, timeline or map selects the concrete chat member. The complete visible callsign and its chat category are retained. KST4Contest then prepares `/cq CALLSIGN` in the send field.
|
||||
|
||||
---
|
||||
|
||||
## Using the Local or Remote QRG
|
||||
|
||||
When a useful propagation direction is detected and a suitable aircraft is available for a candidate, the remote station is often first asked to listen on the local QRG. This is particularly useful while CQ operation is already running there and the station can receive immediately without another change.
|
||||
|
||||
If the remote station does not respond, its own QRG is more suitable or it cannot use the requested frequency, the local station changes frequency. KST4Contest keeps the most recently detected QRGs available, so the operator does not have to search the complete chat history again.
|
||||
|
||||
A deliberate attempt on the QRG of a sked partner is also part of the normal workflow. The objective is not to remain on the local QRG at all costs, but to use the available opportunity with as little delay as possible.
|
||||
|
||||
Before changing frequency, check at least:
|
||||
|
||||
- the correct band,
|
||||
- the complete target callsign,
|
||||
- the remote station’s QRG,
|
||||
- antenna direction,
|
||||
- the expected aircraft scatter window, and
|
||||
- whether the QRG is clear.
|
||||
|
||||
---
|
||||
|
||||
## Using Directional Opportunities
|
||||
|
||||
A directed message between two other stations may indicate that the sender’s antenna is pointing approximately towards the receiver. If this direction is also useful for the local station, KST4Contest temporarily displays the sender in green and bold.
|
||||
|
||||

|
||||
|
||||
The indication appears in the user list and associated views. With an appropriate cluster configuration and a known QRG, the opportunity may additionally be made available through the local DX Cluster.
|
||||
|
||||
Several relevant details are therefore already available when the opportunity appears:
|
||||
|
||||
- complete callsign,
|
||||
- locator and direction,
|
||||
- most recently detected QRG,
|
||||
- band information,
|
||||
- AirScout data, and
|
||||
- the current Reachability or Tropo assessment.
|
||||
|
||||
The operator does not have to collect this information first. The remaining decision is whether the opportunity justifies briefly interrupting ongoing CQ operation.
|
||||
|
||||
At DM5M, practical evaluation of these opportunistic attempts has so far produced a success rate of approximately 35–40%. This value describes the experience of one particular station. It is not a general prediction and depends on factors such as band, distance, station equipment, response time and propagation conditions.
|
||||
|
||||
CQ operation or the next sked can continue immediately after the attempt.
|
||||
|
||||
---
|
||||
|
||||
## Planning and Evaluating Skeds
|
||||
|
||||
Enter a sked with the band actually intended for the contact and a realistic time. KST4Contest adds it to its internal sked management and takes it into account for reminders, the timeline and priority calculation.
|
||||
|
||||
Skeds are maintained only for the current programme session. They are not a persistent replacement for the contest log or operating notes.
|
||||
|
||||
If the Win-Test connection is enabled, KST4Contest additionally attempts to pass the sked to Win-Test. If no usable QRG exists or the band does not match, the internal sked remains available. Only the additional handover may be skipped.
|
||||
|
||||
### Failed 100% Aircraft Skeds
|
||||
|
||||
If a carefully prepared attempt fails despite an aircraft geometry rated at 100% by AirScout, mark the station with **Sked fail**.
|
||||
|
||||
The 100% indication is not a probability of completing the QSO. A failure under these conditions is nevertheless useful operating evidence that the path did not work with the current station configuration and current conditions.
|
||||
|
||||
The mark reduces that station’s priority for the remainder of the current session. This allows candidates without comparable negative operating evidence to be handled first.
|
||||
|
||||
**Sked fail** must not be treated as a permanent statement that the station cannot be worked. Different conditions, another band or a changed station configuration may produce a different result. The mark can be reset and is not retained after restarting the programme.
|
||||
|
||||
---
|
||||
|
||||
## After Every QSO: Logging and the Next Band
|
||||
|
||||
Enter a successful QSO in the connected logging programme immediately. Only then can worked status, band status, filters and priority evaluation be updated in time.
|
||||
|
||||
The available detail depends on the log source. Some interfaces provide band, QRG and locator, while simpler sources report only a global worked state.
|
||||
|
||||
Immediately after every log entry, check whether another common, locally enabled and unworked band is available for the same station. KST4Contest indicates this through `BAND+` and the station’s band information where the available data permits such an evaluation.
|
||||
|
||||
This check is useful in every form of multiband operation. The remote station can be coordinated directly to another band with a specific band and frequency before it turns its antenna away or starts another sked.
|
||||
|
||||
In plain terms: check the next possible QSO while the remote station is still available and the common context still exists.
|
||||
|
||||
If the station is not actually QRV on an indicated band, mark that band as NOT QRV. This removes the unusable opportunity from filters and evaluation instead of allowing it to reappear after every update.
|
||||
|
||||
---
|
||||
|
||||
## Multi-Category and Multiband Operation
|
||||
|
||||
Multi-category operation is not a secondary feature for multiband stations. Its main advantage is that information from two chat categories can be evaluated within one operating workflow.
|
||||
|
||||
The benefit is particularly large for:
|
||||
|
||||
- single-operator multiband stations,
|
||||
- multi-operator multiband stations using one central chat coordinator, and
|
||||
- stations which regularly attempt another band immediately after a QSO.
|
||||
|
||||
Worked state, known band activity and band opportunities can be evaluated together. The concrete message target still retains its complete callsign and chat category.
|
||||
|
||||
This allows the chat operator to coordinate a station directly from the first QSO to another band without searching again for callsign, QRG and band status. In practical operation, this can produce a very rapid sequence of usable QSO opportunities. This is where multi-category operation provides its greatest workflow advantage.
|
||||
|
||||
The same approach remains effective in single-operator operation. The operator still has to change bands personally, but the next useful opportunity is already prepared.
|
||||
|
||||
Multi-multi stations with several active chat operators benefit as well. Responsibilities, band changes and requests already in progress must then be coordinated clearly. Several operators having the same information is useful; several contradictory sked requests sent to the same station are not.
|
||||
|
||||
---
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### DM5M: CQ First, More Skeds Later
|
||||
|
||||
During the first four to five hours of a VHF/UHF contest, DM5M operates mainly by calling CQ. The chat is monitored, but used only for a small number of deliberate interventions.
|
||||
|
||||
Sked activity increases later. Useful propagation directions, suitable aircraft scatter windows, unworked stations and additional band opportunities are then combined deliberately.
|
||||
|
||||
A suitable station is first asked to listen on the local QRG. If it does not respond or cannot use that QRG, DM5M moves to the remote station’s frequency. Planned attempts directly on a sked partner’s QRG are also part of the process.
|
||||
|
||||
A green and bold directional opportunity may briefly interrupt CQ operation. Calling or sked operation continues immediately after the attempt.
|
||||
|
||||
This is a practical example, not a required operating method. Other stations may start arranging skeds considerably earlier, change QRG continuously or make more intensive use of the chat from the beginning.
|
||||
|
||||
### G1YBB: Working Directional Opportunities Systematically
|
||||
|
||||
G1YBB uses the directional indication particularly consistently. Stations highlighted in green are checked systematically and worked where possible while normal CQ operation continues in parallel.
|
||||
|
||||
KST4Contest does not automate the QSO. Its advantage is that QRG, direction, aircraft scatter information and other evaluation data are already available when the opportunity appears. The remaining task is a quick operating decision.
|
||||
|
||||
Another particularly consistent operating method uses the station map as a geographical worklist:
|
||||
|
||||
1. The **wkd** filter removes already worked callsigns from the user list and therefore from the map.
|
||||
2. G1YBB selects an interesting station on the map.
|
||||
3. **Trigger cluster spot** passes the known QRG to Minos.
|
||||
4. Selecting the spot moves Minos to that QRG.
|
||||
5. The completed QSO is logged.
|
||||
6. Log synchronisation updates the Worked state, and the filter removes the station from both the user list and the map.
|
||||
|
||||
The map consequently becomes a spatial list of the stations still to be worked. This workflow is optional. It requires reliable log synchronisation and a working DX Cluster connection and is mainly useful for operators who deliberately want to organise the contest in this geographical way.
|
||||
|
||||
[Watch G1YBB demonstrate this workflow.](https://www.youtube.com/watch?v=lMQZMiSHlUI)
|
||||
|
||||
---
|
||||
|
||||
## Optional Interfaces in the Workflow
|
||||
|
||||
| Interface | Contest task |
|
||||
|---|---|
|
||||
| [Log Synchronisation](en-Log-Sync) | Updates worked and band status after a QSO |
|
||||
| [AirScout](en-AirScout-Integration) | Supplies aircraft scatter candidates and expected time windows |
|
||||
| [PSTRotator](en-Configuration) | Receives or sets the antenna direction |
|
||||
| [Win-Test](en-Log-Sync) | Can additionally pass entered skeds to Win-Test |
|
||||
| [DX Cluster Server](en-DX-Cluster-Server) | Passes detected opportunities to connected logging programmes |
|
||||
| [Station Map](en-User-Interface) | Shows stations, directions, selection and radio path |
|
||||
|
||||
None of these interfaces is mandatory for basic chat operation. Their value appears when they are configured reliably and shorten a specific manual task.
|
||||
|
||||
---
|
||||
|
||||
## What KST4Contest Does Not Decide
|
||||
|
||||
KST4Contest does not decide:
|
||||
|
||||
- whether a QRG is actually clear,
|
||||
- whether the remote station can currently listen,
|
||||
- whether an aircraft will enable a QSO,
|
||||
- whether a calculated radio path works under the current conditions,
|
||||
- whether ongoing CQ operation should be interrupted for an opportunity, or
|
||||
- which candidate has the greatest value for the current contest strategy.
|
||||
|
||||
The programme assembles the available information and keeps it current. The final decision remains with the operator. This is not a limitation of the workflow; it is the part for which we are still operating radios rather than spreadsheets.
|
||||
@@ -2,75 +2,262 @@
|
||||
|
||||
> 🇬🇧 You are reading the English version | 🇩🇪 [Deutsche Version](de-DX-Cluster-Server)
|
||||
|
||||
From **version 1.23**, KST4Contest includes a built-in DX cluster server. It sends spots directly to the logging software whenever a direction warning is triggered.
|
||||
Since version 1.23, KST4Contest has included a local DX Cluster server. It forwards detected directional opportunities and their frequencies to the DX Cluster client of a logging programme.
|
||||
|
||||
*(Idea by OM0AAO, Viliam Petrik – thank you!)*
|
||||
The idea came from OM0AAO, Viliam Petrik. Thank you!
|
||||
|
||||
---
|
||||
|
||||
## What is the Built-in DX Cluster Server For?
|
||||
## Why Use a Local DX Cluster Server?
|
||||
|
||||
When KST4Contest detects that a station is requesting a sked from your direction and a QRG is known, it **automatically generates a DX cluster spot** and feeds it directly to the logging software's cluster client / band map.
|
||||
Finding an interesting frequency in the chat is only the first step. During a contest, that information needs to reach the place where it can be used immediately: the logging programme and its bandmap.
|
||||
|
||||
The logging software then displays the spot in the band map. Clicking the spot sets the transceiver's frequency and mode directly – without any manual typing.
|
||||
KST4Contest therefore combines two pieces of information it already has:
|
||||
|
||||
1. A directed chat message can indicate the approximate direction in which the sending station may be pointing its antenna.
|
||||
2. A frequency for that station may be known from the same message or an earlier one.
|
||||
|
||||
When both pieces fit, KST4Contest creates a local DX Cluster spot. The logger can display it in its bandmap and, depending on its own configuration, tune the transceiver to the frequency when the spot is clicked.
|
||||
|
||||
In practical terms, the operator does not have to find the information in the chat, read it, remember it and enter it again in the logger. These small interruptions consume a surprising amount of attention during a contest.
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
## Automatic Spots from Directional Opportunities
|
||||
|
||||
### In KST4Contest
|
||||
Assume that station A sends a directed message to station B. KST4Contest uses the direction from A to B as an approximation of the current antenna direction of station A. It then checks whether the local station lies inside the assumed antenna corridor as seen from A.
|
||||
|
||||
In Preferences → **DX Cluster Server Settings**:
|
||||
Two directions are compared:
|
||||
|
||||
1. Enter the **port** of the internal server (e.g. 7300 or 8000 – must match the logging software).
|
||||
2. Enter a **spotter callsign** – **this must be a different callsign than your contest callsign!**
|
||||
- Reason: Logging programs filter spots from your own callsign as "already worked". If the spotter uses the same callsign, the spots will not be displayed.
|
||||
3. Enter the **assumed MHz**: For frequency references like ".205" in the chat, KST4Contest needs to decide whether 144.205, 432.205 or 1296.205 is meant. For single-band contests, simply enter the corresponding band centre. Full frequency references like "144.205" or "1296.338" in the chat are always correctly identified.
|
||||
- the direction from station A to station B;
|
||||
- the direction from station A to the local station.
|
||||
|
||||
### In UCXLog
|
||||
The **Antenna Beamwidth** configured under the Station settings is the complete angle. Half of that value is applied on either side of the direction A → B. A setting of `70°` therefore produces a corridor of `±35°`.
|
||||
|
||||
- Configure a DX cluster server connection:
|
||||
- Host: `127.0.0.1` (or IP of the KST4Contest computer)
|
||||
- Port: As configured in KST4Contest
|
||||
- Password: can be left empty
|
||||
- Use the **"Send a test message to your log"** button to test the connection.
|
||||
ON4KST does not supply antenna data for the remote station. KST4Contest therefore also uses the locally configured beamwidth as an approximation for station A. This is not a measurement of the station's actual antenna direction. It is a deliberately simple geometrical assumption.
|
||||
|
||||
### In N1MM+
|
||||
An automatic DX Cluster spot is created only when all of the following conditions are met:
|
||||
|
||||
Similar settings:
|
||||
- Host: `127.0.0.1` (or IP of the KST4Contest computer)
|
||||
- Port: As configured in KST4Contest
|
||||
1. A directed message between two other stations has been detected.
|
||||
2. Valid locators are known for the sender and receiver.
|
||||
3. The sender is within the configured **Default Maximum QRB** from the local station.
|
||||
4. The local station lies inside the assumed antenna corridor as seen from the sender.
|
||||
5. A usable frequency is known for the sender or detected in the current message.
|
||||
6. The local DX Cluster server is enabled.
|
||||
|
||||
When these conditions are met, the spot is created while the message is processed. The green directional highlight shown in parallel remains visible for five minutes and may be extended or removed by later messages.
|
||||
|
||||
The calculation does not consider terrain, current propagation or the station's actual operating intention. It identifies a plausible opportunity. The full derivation and a numerical example are available under [Directional Opportunities from Directed Messages](en-Features#directional-opportunities-from-directed-messages).
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
## Manual Spot for the Selected Map Station
|
||||
|
||||
A spot is generated when **both** conditions are met:
|
||||
A spot can also be triggered deliberately. Select a station on the station map and use **Trigger cluster spot** in the detail panel.
|
||||
|
||||
1. A **direction warning** has been triggered (station is making a sked in your direction).
|
||||
2. The **station's QRG is known** (read from the chat or manually entered).
|
||||
This manual action does not require a previously detected directed message. The maximum QRB and antenna beamwidth also do not decide whether the spot is sent. It requires:
|
||||
|
||||
The generated spot contains:
|
||||
- Station's callsign
|
||||
- Frequency
|
||||
- Spot time
|
||||
- the local DX Cluster server to be enabled;
|
||||
- at least one connected DX Cluster client; and
|
||||
- a usable QRG for the selected map station.
|
||||
|
||||
The logging software can then display the spot in the band map and tune the TRX to that frequency with a mouse click.
|
||||
This lets the operator send an already selected station to the bandmap even when the conditions for an automatic directional spot are not present. Map operation is described under [Station Map](en-User-Interface#station-map).
|
||||
|
||||
Both automatic and manual spots are sent only to clients connected to KST4Contest. They are not forwarded to a public Internet cluster.
|
||||
|
||||
---
|
||||
|
||||
## Multi-Computer Setup
|
||||
## Which Frequency Is Used?
|
||||
|
||||
If KST4Contest runs on a separate computer (not the logging computer):
|
||||
A DX Cluster spot needs an unambiguous frequency. KST4Contest uses the same QRG detection as the user list and the other band-related functions.
|
||||
|
||||
- Host in the logging software: IP of the KST4Contest computer (not `127.0.0.1`)
|
||||
- Same configuration as for the QSO UDP broadcast packets (see [Log Synchronisation](en-Log-Sync))
|
||||
Complete frequencies determine their band directly:
|
||||
|
||||
```text
|
||||
144.205
|
||||
432,088
|
||||
1296.338
|
||||
10368.100
|
||||
```
|
||||
|
||||
Relative values contain only the frequency part within a band:
|
||||
|
||||
```text
|
||||
.205
|
||||
,205
|
||||
qrg 205
|
||||
freq is 205
|
||||
on 205
|
||||
205 MHz
|
||||
```
|
||||
|
||||
A bare three-digit number such as `205` is not evaluated without frequency-related context. The same applies to `599`, `144` or text such as `worked 210 stations`. This prevents signal reports, band names or QSO totals from being stored as plausible-looking frequencies and later sent to the logger.
|
||||
|
||||
For a relative QRG, KST4Contest determines the band in this order:
|
||||
|
||||
1. It checks whether a suitable band context has been detected for the same sender during the previous 30 minutes.
|
||||
2. If several current bands are known, it uses the most recently updated plausible context.
|
||||
3. Only when no suitable station context exists does it use the band selected under **Fallback band for relative QRG detection**.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Global fallback: 144 MHz
|
||||
Most recent complete QRG for the station: 432.088 MHz
|
||||
New chat value from the same station: .100
|
||||
Detected QRG: 432.100 MHz
|
||||
DX Cluster frequency: 432100.0 kHz
|
||||
```
|
||||
|
||||
Without the current 432 MHz context, the same value would use the global fallback and become `144.100 MHz`.
|
||||
|
||||
QRG detection runs before the direction and spot checks. If a station mentions its frequency for the first time in the directed message which also triggers a directional opportunity, the resulting spot can already contain that frequency. A newly detected QRG replaces an older value for the station.
|
||||
|
||||
The fallback band is a global QRG-detection setting. Its effect is not limited to the DX Cluster server. Configuration, supported bands and related behaviour are described under [Fallback Band for Relative QRG Detection](en-Configuration#fallback-band-for-relative-qrg-detection).
|
||||
|
||||
---
|
||||
|
||||
## Tested Logging Software
|
||||
## Setting Up KST4Contest
|
||||
|
||||
- **UCXLog** ✓
|
||||
- **N1MM+** ✓
|
||||
Open the **Notification** tab in Preferences.
|
||||
|
||||
Further test reports are welcome – please send by email to DO5AMF.
|
||||

|
||||
|
||||
Configure the following:
|
||||
|
||||
1. Enable **Enable the local DX Cluster server …**.
|
||||
2. Enter a free **TCP port**. The default is `8000`.
|
||||
3. Select the appropriate band under **Fallback band for relative QRG detection**.
|
||||
4. Enter a **Spotter callsign**.
|
||||
|
||||
The spotter callsign should preferably differ from the contest callsign. Some loggers filter spots which appear to originate from the local station or handle them differently. Using the same callsign is not prohibited by KST4Contest, but it may make a correctly generated spot invisible in the bandmap.
|
||||
|
||||
Changes to the enabled state and TCP port take effect immediately while KST4Contest is connected to the chat. Changing the port disconnects existing DX Cluster clients; the logger must reconnect to the new port.
|
||||
|
||||
Use **Save Settings** to store the settings permanently in `preferences.xml`.
|
||||
|
||||
---
|
||||
|
||||
## Setting Up the Logging Programme
|
||||
|
||||
Configure the logging programme as a DX Cluster client connected to KST4Contest.
|
||||
|
||||
| Setting | KST4Contest and logger on the same computer | Logger on another computer |
|
||||
|---|---|---|
|
||||
| Host | `127.0.0.1` | IP address of the KST4Contest computer |
|
||||
| Port | TCP port configured in KST4Contest | TCP port configured in KST4Contest |
|
||||
| Login | Any callsign, if the logger requires one | Any callsign, if the logger requires one |
|
||||
| Password | Not required | Not required |
|
||||
|
||||
KST4Contest does not use the login sent by the logger for authentication. The connection is intended for the local computer or a trusted station network.
|
||||
|
||||
If the logger runs on another computer, its TCP connection must be allowed through the local firewall on the KST4Contest computer. Do not expose the port directly to the Internet without additional protection.
|
||||
|
||||
Several DX Cluster clients can be connected at the same time. Every generated spot is sent to all clients which are currently connected.
|
||||
|
||||
---
|
||||
|
||||
## Testing the Connection
|
||||
|
||||
The **Send test spot** button creates the following test entry:
|
||||
|
||||
```text
|
||||
Spotted callsign: DO5AMF
|
||||
Comment: DXC test: You donated $100!
|
||||
Frequency: .300 on the configured fallback band
|
||||
```
|
||||
|
||||
With `144 MHz` selected as the fallback band, the spot therefore appears at approximately `144.300 MHz`.
|
||||
|
||||
The comment is a deliberately retained Easter egg. It only makes the test spot easy to recognise in the logger. No donation or other external action is triggered.
|
||||
|
||||
Three conditions must be met before running the test:
|
||||
|
||||
1. KST4Contest is connected to the ON4KST chat.
|
||||
2. The local DX Cluster server is enabled.
|
||||
3. The logging programme's DX Cluster client is connected to KST4Contest.
|
||||
|
||||
If no client is connected, KST4Contest displays a corresponding message. A successful test therefore confirms that at least one connected client received the generated spot.
|
||||
|
||||
---
|
||||
|
||||
## Content of a Generated Spot
|
||||
|
||||
A spot contains:
|
||||
|
||||
- the configured spotter callsign;
|
||||
- the normalised frequency;
|
||||
- the complete visible callsign of the detected or selected station;
|
||||
- the locator; and
|
||||
- the current UTC time.
|
||||
|
||||
The payload line uses a fixed, DXSpider-compatible 75-character format. The DX callsign starts in column 27, the comment field is exactly 30 characters wide and the UTC time starts in column 71. Short comments are padded with spaces; longer ones are deliberately limited to 30 characters. Different spotter-callsign lengths and frequencies up to 24 GHz do not move the following fields.
|
||||
|
||||
KST4Contest does not truncate the complete DX callsign. If it exceeds twelve characters, the spot is rejected in a controlled manner and the reason is logged.
|
||||
|
||||
For automatically generated directional spots, KST4Contest can add up to two current AirScout entries to the comment. Missing AirScout data does not prevent the spot from being sent. A spot triggered manually from the station map uses the selected station's locator without this optional addition.
|
||||
|
||||
An automatic comment with AirScout information may look like this:
|
||||
|
||||
```text
|
||||
JO51HK AP 1m/100%;4m/75%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## If No Spot Appears
|
||||
|
||||
### The Test Spot Does Not Reach the Logger
|
||||
|
||||
Check:
|
||||
|
||||
- Is KST4Contest connected to the chat?
|
||||
- Is the local DX Cluster server enabled?
|
||||
- Does the logger use the same TCP port?
|
||||
- Does the logger use `127.0.0.1` when both programmes run locally?
|
||||
- Is a firewall blocking the connection?
|
||||
- Is the DX Cluster window or bandmap enabled in the logger?
|
||||
|
||||
### The Test Works, but Automatic Spots Are Missing
|
||||
|
||||
The TCP connection is then working in principle. At least one condition for the relevant chat situation was probably not met:
|
||||
|
||||
- no directed message between two other stations;
|
||||
- missing locator;
|
||||
- sender outside the maximum QRB;
|
||||
- direction outside the configured beamwidth;
|
||||
- no detected frequency.
|
||||
|
||||
KST4Contest deliberately does not send every frequency it finds to the logger. Otherwise, a feature intended to reduce distraction would quickly become a local spot generator with rather too much enthusiasm.
|
||||
|
||||
### A Manually Triggered Spot Is Missing
|
||||
|
||||
Check that a station is selected on the map and that it has a usable QRG. The local server must be enabled and at least one client must be connected. Directional-message geometry, maximum QRB and beamwidth are not prerequisites for the manual action.
|
||||
|
||||
### The Spot Appears on the Wrong Band
|
||||
|
||||
First check which frequencies were detected for the station during the previous 30 minutes. For a relative value, this station context takes priority over the global fallback.
|
||||
|
||||
If no current station context exists, check **Fallback band for relative QRG detection**. The fallback is used only when the band cannot be determined from a complete frequency or the sender's current context.
|
||||
|
||||
### The Logger Hides the Spot
|
||||
|
||||
Try a spotter callsign which differs from the contest callsign. Depending on the logger, spots from the local callsign may be filtered or handled specially. KST4Contest itself does not require the two callsigns to differ.
|
||||
|
||||
---
|
||||
|
||||
## Tested Logging Programmes
|
||||
|
||||
The interface has been used with:
|
||||
|
||||
- UCXLog
|
||||
- N1MM+
|
||||
- Minos
|
||||
|
||||
Other loggers may work if they support a normal TCP connection to a DX Cluster server and accept conventional `DX de ...` spot lines.
|
||||
|
||||
Related reference pages:
|
||||
|
||||
- [User Interface](en-User-Interface)
|
||||
- [Features](en-Features)
|
||||
- [Configuration](en-Configuration)
|
||||
|
||||
@@ -6,45 +6,122 @@ Overview of all main features of KST4Contest.
|
||||
|
||||
---
|
||||
|
||||
## Sked Direction Highlighting
|
||||
## Directional Opportunities from Directed Messages
|
||||
|
||||
One of the core features: when a station makes a sked request **towards your direction**, it is highlighted **green and bold** in the user list.
|
||||
The ON4KST chat shows which station directs a message to which other station. It does not transmit the actual antenna direction. A directed message can nevertheless provide a useful indication during a contest: a station requesting, answering or preparing a sked will normally point its antenna at least approximately towards the station being addressed.
|
||||
|
||||
### How does it work?
|
||||
KST4Contest therefore evaluates directed messages between two other stations. The message does not have to be explicitly identified as a sked request. The relevant information is the sender, receiver and their locators.
|
||||
|
||||
The calculation is based on the following logic:
|
||||
### How Is the Direction Derived?
|
||||
|
||||
- When station A sends a sked request to station B, it is assumed that A is pointing its antenna towards B.
|
||||
- If the resulting direction from A to your own station is within half the beamwidth of your own antenna, A is highlighted.
|
||||
Assume that station A sends a directed message to station B:
|
||||
|
||||
**Example** (beamwidth 69°, half-angle 34.5°):
|
||||
1. KST4Contest calculates the direction from station A to station B.
|
||||
2. This direction is used as the likely antenna direction of station A.
|
||||
3. KST4Contest then calculates the direction from station A to the local station.
|
||||
4. The angular difference is compared with half of the configured antenna beamwidth.
|
||||
5. Station A must also be within the configured maximum QRB.
|
||||
|
||||
| Situation | Result for DO5AMF in JN49 |
|
||||
A configured beamwidth of `70°` therefore produces an assumed corridor of `35°` on either side of the direction from station A to station B.
|
||||
|
||||

|
||||
|
||||
In the example, the green corridor runs from F5FEN towards DM5M. When F5FEN sends a directed message to DM5M, KST4Contest checks whether the local station lies inside this corridor. A reply from DM5M to F5FEN is calculated again in the opposite direction: the blue corridor then starts at DM5M and points towards F5FEN. Each message therefore evaluates the assumed antenna direction of its sender.
|
||||
|
||||
| Example | Result |
|
||||
|---|---|
|
||||
| Sked from F5FEN → DM5M | ✅ Highlighted (F5FEN points towards DM5M, close to JN49) |
|
||||
| Sked from DM5M → F5FEN | ✅ Highlighted (DM5M replies towards F5FEN) |
|
||||
| F1DBN is uninvolved | ❌ No highlighting |
|
||||
| DO5AMF/P (different location) | ❌ No highlighting for sked reply |
|
||||
| Direction A → B: `120°`, direction A → local station: `145°` | Angular difference `25°`: directional opportunity detected |
|
||||
| Direction A → B: `120°`, direction A → local station: `165°` | Angular difference `45°`: outside the assumed corridor |
|
||||
| Locator of A or B is missing | Direction cannot be calculated |
|
||||
| A is outside the maximum QRB | No directional opportunity |
|
||||
|
||||
The calculation does not include topographic path calculations – this is a deliberate simplification. It may be added in a future version.
|
||||
### What Is Shown in the User List?
|
||||
|
||||
> Configuration: [Configuration – Antenna Beamwidth](Configuration#antenna-beamwidth)
|
||||
When a directional opportunity is detected, the sender's callsign appears in green and bold in the user list. Evening mode uses a lighter green. The receiver is not marked merely because it received the message; a reply in the opposite direction is evaluated as a separate message and therefore as a new case.
|
||||
|
||||

|
||||
|
||||
In the screenshot, DF0GEB sent a directed message to DN9APW and received a reply. KST4Contest detected the directional opportunity and highlighted DN9APW in the user list. The map is shown for context: the local station lies between the two stations and therefore receives the indication.
|
||||
|
||||
The mark remains visible for five minutes after the most recent matching message. Another matching message from the same station restarts that period. If the station sends another directed message which no longer satisfies the direction conditions, the mark is removed immediately.
|
||||
|
||||
If simple sound notifications are enabled, KST4Contest also plays a short indication when the directional opportunity is first detected. Further matching messages do not repeat the same sound while the station is already marked.
|
||||
|
||||
### What Does the Mark Mean – and What Does It Not Mean?
|
||||
|
||||
The calculation is a geometric derivation. It does not prove that station A is actually pointing its antenna towards station B. It also does not include terrain, current propagation, the real antenna pattern of the remote station or its rotator position.
|
||||
|
||||
ON4KST does not provide the beamwidth of the remote station. KST4Contest therefore uses the value configured for the local antenna as an approximation for station A. A value which is too large produces more possible directional opportunities with less practical meaning. A value which is too small may hide useful situations.
|
||||
|
||||
In plain terms: the green mark is a reasoned indication of a possible opportunity. It is neither a propagation forecast nor a guarantee of completing a QSO.
|
||||
|
||||
Configuration:
|
||||
|
||||
- [Antenna Beamwidth](en-Configuration#antenna-beamwidth)
|
||||
- [Default Maximum QRB](en-Configuration#default-maximum-qrb)
|
||||
|
||||
---
|
||||
|
||||
## Sked Direction Spots (Built-in DX Cluster)
|
||||
## Forwarding as a DX Cluster Spot
|
||||
|
||||
From **v1.23**: Direction warnings are forwarded as DX cluster spots to the logging software when a QRG is known. Details: [DX Cluster Server](en-DX-Cluster-Server).
|
||||
Since version 1.23, KST4Contest can forward a detected directional opportunity to the DX Cluster client of a logging programme. The local DX Cluster server must be enabled and a usable frequency must be known for the sender.
|
||||
|
||||
The frequency may come from an earlier message or may be detected for the first time in the message which triggers the opportunity. In both cases it is available when KST4Contest checks whether it can create a spot. The programme therefore does not forward every QRG found in the chat. Automatic spots require a geometrically matching directed message.
|
||||
|
||||
The five-minute user-list mark and the DX Cluster spot use the same direction calculation but have different lifetimes. The mark remains visible temporarily. The spot is generated immediately while the matching message is processed.
|
||||
|
||||
Setup, frequency handling and limitations: [Built-in DX Cluster Server](en-DX-Cluster-Server).
|
||||
|
||||
---
|
||||
|
||||
## QRG Detection (QRG Reading)
|
||||
## QRG Detection
|
||||
|
||||
KST4Contest processes every line of text flowing through the channel and automatically extracts **frequency references**. These are displayed in the user list in the **QRG column**.
|
||||
Frequencies are rarely written in a consistent form in the ON4KST chat. A station may first mention `432.088`, later write only `.100`, and use `qrg 120` in another message. The context is usually obvious to a human reader. Software still has to distinguish whether `120` is a frequency, a time, a distance or something else entirely.
|
||||
|
||||
Recognised formats: `144.205`, `432.088`, `.205` (with configured band assumption), etc.
|
||||
KST4Contest therefore evaluates the text of every public and directed chat message. A detected QRG is assigned to the sender and displayed in the user list's **QRG column**. The column shows the most recently detected frequency with at least three decimal places. A value stored internally as `144.21` is therefore displayed as `144.210`.
|
||||
|
||||
**Benefit**: Without asking, you can directly look up a station's calling frequency and decide whether a contact is possible.
|
||||
### Which Formats Are Recognised?
|
||||
|
||||
| Notation | Example | Processing |
|
||||
|---|---|---|
|
||||
| Complete frequency | `144.210`, `432,088`, `144307`, `10368100` | The frequency determines the band directly. |
|
||||
| Relative frequency with a dot or comma | `.210`, `,088` | The band is added from the station context or configured fallback. |
|
||||
| Three-digit frequency with text context | `qrg 210`, `freq is 210`, `on 210`, `210 MHz` | The number is treated as a relative frequency. |
|
||||
| Three-digit number without frequency context | `210`, `599`, `144` | The number is deliberately not accepted as a QRG. |
|
||||
|
||||
The final restriction prevents plausible-looking but incorrect results. With a fallback of `144 MHz`, a signal report of `599` could easily be turned into `144.599 MHz`. The result would be formally valid and operationally useless.
|
||||
|
||||
A complete frequency may also be written without a dot or comma. KST4Contest treats the final three digits as the kHz part: `144307` in a station name becomes `144.307 MHz`, while `10368100` in a public or directed chat message becomes `10368.100 MHz`. The value is accepted only if the resulting frequency falls within a supported band range.
|
||||
|
||||
### How Is the Band of a Relative QRG Determined?
|
||||
|
||||
KST4Contest uses the following order:
|
||||
|
||||
1. If a suitable complete frequency has been detected for the same sender during the previous 30 minutes, KST4Contest uses its band.
|
||||
2. If several current bands are known, the most recently updated plausible band context is used.
|
||||
3. If no suitable station context exists, KST4Contest uses the band selected under **Fallback band for relative QRG detection**.
|
||||
|
||||
Example: the global fallback is `144 MHz`. A station first mentions `432.088` and writes `.100` a few minutes later. KST4Contest does not add the global fallback. It uses the more recent station context, producing `432.100 MHz`. If another station without previous band information writes `.100`, the result is `144.100 MHz`.
|
||||
|
||||
The fallback band really is the last resort. It is selected from the bands supported by KST4Contest and affects all QRG detection, not only the built-in DX Cluster server.
|
||||
|
||||
### Where Is a Detected QRG Used?
|
||||
|
||||
The most recently detected frequency appears in the user list. Its band context can also affect other functions, including:
|
||||
|
||||
- detection of the station's active bands;
|
||||
- the chat-member score and priority lists;
|
||||
- band-upgrade hints after a log entry;
|
||||
- frequency selection for skeds; and
|
||||
- a DX Cluster spot generated from a directional opportunity.
|
||||
|
||||
If the QRG first appears in the same message which triggers a directional opportunity, it is processed before the direction and spot checks. The resulting spot can therefore already use the frequency from that message.
|
||||
|
||||
Detection remains a text-based process. KST4Contest cannot prove that the station is still using the stated frequency or that an ambiguous value belongs to a different context. This is precisely why bare three-digit numbers without frequency-related text are no longer accepted.
|
||||
|
||||
Configuration and supported fallback bands: [Fallback Band for Relative QRG Detection](en-Configuration#fallback-band-for-relative-qrg-detection).
|
||||
|
||||
Use in a logger bandmap: [Built-in DX Cluster Server](en-DX-Cluster-Server).
|
||||
|
||||
---
|
||||
|
||||
@@ -62,7 +139,7 @@ This distinction matters. A callsign already worked on one band may still be use
|
||||
|
||||
[Log Synchronisation](en-Log-Sync) imports new QSOs from the logging application. The amount of information available depends on the interface being used:
|
||||
|
||||
- The file-based Simplelogfile interpreter detects callsigns only. It can therefore set only the global Worked status.
|
||||
- The file-based Simplelogfile interpreter reads the selected text file once per minute using a fixed callsign pattern. Matches are normalised to the base callsign and set only the global Worked status for all active variants.
|
||||
- The QSO UDP interfaces and the Win-Test network listener can also provide the band.
|
||||
- If the log packet contains a valid locator, KST4Contest additionally stores the worked four-character grid square for that band.
|
||||
|
||||
@@ -131,7 +208,9 @@ In plain terms: an automatically detected hint means "probably active on this ba
|
||||
|
||||
### Storage and lifetime
|
||||
|
||||
Worked, NOT-QRV and worked-grid information is stored in the internal SQLite database and restored on the next start. Entries expire automatically after three days, so a reset before every contest is normally unnecessary.
|
||||
Worked, NOT-QRV and worked-grid information received from network interfaces, together with manual marks, is stored in the internal SQLite database and restored on the next start. Entries expire automatically after three days, so a reset before every contest is normally unnecessary.
|
||||
|
||||
Simplelogfile matches are excluded. They are not persisted in SQLite but are derived from the selected file during each application session. The file is therefore the durable source. The interpreter does not remove an existing Worked mark during the current session and does not reset the state automatically for a new contest. A manual database reset does not change or empty the file. Callsigns contained in it are marked as worked again when the file is next evaluated within one minute.
|
||||
|
||||
A manual reset under **Workedstn database** removes all Worked marks, NOT-QRV marks and stored worked grid squares. The known callsign rows remain in the database. See [Worked Station Database Settings](en-Configuration#worked-station-database-settings) for details.
|
||||
|
||||
@@ -161,19 +240,33 @@ The filters above the user list use the same information as the Worked columns:
|
||||
|
||||
Several active filters are applied together. A station remains visible only if it satisfies every selected condition. The filters react immediately to new log entries and changed NOT-QRV marks.
|
||||
|
||||
Operation and layout of the filter bar: [User Interface – Filters](en-User-Interface#filters).
|
||||
Operation and layout of the filter bar: [User Interface – Filters](en-User-Interface#filters-and-reachability-controls).
|
||||
|
||||
---
|
||||
|
||||
## Coloured PM Rows (from v1.25)
|
||||
|
||||
New private messages appear in **red**. The colour fades every 30 seconds from yellow to white – like a rainbow fade. This makes it immediately clear how recent a message is.
|
||||
New incoming private messages use a series of green age highlights. The green becomes progressively more muted as the message gets older:
|
||||
|
||||
| Message age | Display |
|
||||
|---|---|
|
||||
| up to and including 30 seconds | first green level |
|
||||
| 31 to 60 seconds | second green level |
|
||||
| 61 to 90 seconds | third green level |
|
||||
| 91 to 120 seconds | fourth green level |
|
||||
| 121 to 180 seconds | fifth green level |
|
||||
| 181 to 300 seconds | sixth green level |
|
||||
| from 301 seconds | normal table colour |
|
||||
|
||||
The table refreshes the age display every five seconds, so a boundary may become visible only during the next refresh. No age class remains attached after five minutes; reused and empty table rows also return to their normal style.
|
||||
|
||||
Messages sent by the local station retain their separate highlight and do not use the green age scale.
|
||||
|
||||
*(Idea by IU3OAR, Gianluca Costantino – thank you!)*
|
||||
|
||||
---
|
||||
|
||||
## PM Catching
|
||||
## PM Catching (from v1.1)
|
||||
|
||||
Some users accidentally post direct messages publicly, e.g.:
|
||||
|
||||
@@ -181,27 +274,58 @@ Some users accidentally post direct messages publicly, e.g.:
|
||||
(DM5M) pse ur qrg
|
||||
```
|
||||
|
||||
KST4Contest detects such messages that contain your own callsign and automatically sorts them into the **private messages table**. No messages are missed this way.
|
||||
KST4Contest searches the text of every message not sent by the local station for the configured local login callsign without distinguishing upper- and lower-case letters. A match also appears in the **private messages table**. This applies to public messages addressed to `ALL` and to directed messages between other chat participants.
|
||||
|
||||
A directed message between two other stations is where the slightly flippant description **“gossip detection”** fits best. It is a nickname, not the formal name of the function.
|
||||
|
||||
The original message remains unchanged: its actual receiver remains `ALL` or the other chat participant; its text, chat category and routing are retained as well. PM Catching merely adds another view of the same message.
|
||||
|
||||
The text search cannot infer intention. A typing error or shortened callsign is not recognised. Conversely, a simple mention of the complete callsign can produce a match even when no reply was expected.
|
||||
|
||||
Selecting an incoming row in the PM table prepares a reply to its sender. For an outgoing message from the local station, the original receiver is restored as the target instead. Selecting the row does not send the message.
|
||||
|
||||
Caught rows use the normal PM-table age display. They do not trigger the simple PM sound, CW callsign output or phonetic callsign output. The same applies to messages shown additionally through QSO Monitoring.
|
||||
|
||||
---
|
||||
|
||||
## Automatic Replies to Private Messages (from v1.25)
|
||||
|
||||
Not every station logged into the ON4KST chat is taking part in the current contest. Sked requests may nevertheless be sent to many visible callsigns without first checking whether they are participating. Without an automatic reply, those stations would have to enter the same refusal repeatedly.
|
||||
|
||||
KST4Contest can answer such private messages with a predefined text. The incoming message remains visible; it is neither blocked nor discarded. A separate QRG reply recognises common requests such as `qrg?`, `freq?` and `pse qrg`.
|
||||
|
||||
When two chat categories are connected, the original context is retained. The reply is addressed to the complete sender callsign and sent through the category in which the request arrived. A QRG request receives only the QRG belonging to that category.
|
||||
|
||||
If the required QRG is missing, KST4Contest sends no incomplete answer. Empty or locally invalid general reply texts are rejected as well.
|
||||
|
||||
Automatic replies need limits. KST4Contest adds the fixed prefix `[KST4C Automsg]`, ignores incoming messages which already contain that prefix and permits only one automatic reply to the same complete callsign in the same category within two minutes. The cooldown is shared by the general and QRG-specific reply functions.
|
||||
|
||||
A rejected reply does not start the cooldown. After entering the missing QRG or correcting the configured text, KST4Contest can therefore answer the next request immediately.
|
||||
|
||||
In plain terms: the function cannot prevent indiscriminate sked requests. It prevents the recipient from having to answer every one of them with the same refusal. It is not intended to simulate a conversation, let alone start an endless discussion with another automatic client.
|
||||
|
||||
Configuration, recognised QRG requests and category handling: [Configuration – Messagehandling Settings](en-Configuration#messagehandling-settings-from-v125).
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Multi-Channel Login (from v1.26)
|
||||
|
||||
Simultaneous login to **two chat categories** (e.g. 144 MHz and 432 MHz). Both chats are monitored in parallel.
|
||||
KST4Contest signs in to ON4KST once with one local login callsign and password. One locator is shared by the entire TCP session. A second chat category (e.g. 144 MHz and 432 MHz) is added through Single Sign-on within the same session. Both categories are monitored in parallel, while the visible category-specific name field, message context, QRG and beacon remain separate for each category.
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode (from v1.26)
|
||||
|
||||
Toggle via: **Window → Use Dark Mode**
|
||||
Enable it through **Windows → Use dark mode design**. Use **Windows → Use default mode design** to return to the normal light colour scheme.
|
||||
|
||||
For individual colour adjustments: edit the CSS file (path in the program settings).
|
||||
The green private-message age scale remains available in both designs. Text colour, normal table colour and the separate highlight for locally sent messages follow the selected built-in design.
|
||||
|
||||
---
|
||||
|
||||
## Opposite Station Multi-Callsign Login Tagging (from v1.26)
|
||||
|
||||
Support for stations that are active in the chat with multiple callsigns simultaneously (e.g. expedition setups).
|
||||
Support for remote stations which are active in the chat under several complete visible callsign variants at the same time (e.g. expedition setups). The complete callsign and chat category remain separate participant identities, while Worked, band and priority data are shared through the base callsign.
|
||||
|
||||
---
|
||||
|
||||
@@ -213,7 +337,7 @@ For selected stations in the user list, there are direct buttons to open the **Q
|
||||
|
||||
## Skeds and Sked Reminders
|
||||
|
||||
> Available from v1.40; band, callsign and Win-Test handling extended in Nightly / v1.42.
|
||||
> Available from v1.40; band, callsign and Win-Test handling extended in v1.42.
|
||||
|
||||
A sked is more than a reminder tied to a particular time. During a contest, it must become visible early enough, move the agreed station up the priority list and – if required – remind the remote station as well.
|
||||
|
||||
@@ -294,10 +418,49 @@ Win-Test handover: [Log Synchronisation – Win-Test](en-Log-Sync#win-test)
|
||||
|
||||
## QSO Sniffer (from v1.31)
|
||||
|
||||
The QSO sniffer monitors the chat for messages from a configurable callsign list and automatically forwards them to the **PM window**. This prevents relevant messages from being lost in the general chat traffic.
|
||||
During a contest, it may be useful to follow the communication of one particular station. This is not limited to rare stations or DXpeditions. Sked arrangements made by another station in the same team can disappear just as easily in a busy public chat.
|
||||
|
||||
KST4Contest can therefore show every message sent or received by a monitored station additionally in the PM table. The message remains in its original table at the same time.
|
||||
|
||||
Monitoring uses the normalised base callsign. An entry for `DN9APW` consequently covers messages sent by or addressed to:
|
||||
|
||||
```text
|
||||
DN9APW-2
|
||||
DN9APW-70
|
||||
DN9APW-144
|
||||
DN9APW-432
|
||||
```
|
||||
|
||||
Entering `DN9APW-70` in the monitoring list still stores `DN9APW`. A station using several band- or operating-position-specific KST suffixes therefore needs only one entry.
|
||||
|
||||
The actual message retains the complete callsigns:
|
||||
|
||||
```text
|
||||
Sniffed: (DN9APW-70 > 9A0BB-23) pse sked 19:30
|
||||
```
|
||||
|
||||
This distinction is intentional. Monitoring aggregates the variants so that their communication remains visible. Message routing does not aggregate them because the intended login would otherwise become ambiguous.
|
||||
|
||||
The base callsign is evaluated across both chat categories in the shared ON4KST session. The category of each individual message remains unchanged.
|
||||
|
||||
The monitoring view includes:
|
||||
|
||||
- directed messages sent by a monitored station;
|
||||
- directed messages addressed to a monitored station; and
|
||||
- public messages sent by a monitored station to `ALL`.
|
||||
|
||||
A callsign which merely occurs in the message text does not trigger monitoring.
|
||||
|
||||
Messages already addressed directly to the local callsign remain normal private messages and are not additionally marked as `Sniffed:`. QSO monitoring also does not generate a separate notification sound. Sound notifications for private messages actually addressed to the local station remain independent.
|
||||
|
||||
The function does not request additional messages from the ON4KST server. It only provides another view of chat traffic which KST4Contest has already received.
|
||||
|
||||
In plain terms: QSO monitoring does not decide whether a message really contains a sked or matters to the local contest operation. It makes the communication of the selected station easier to find. The operator still decides what to do with it.
|
||||
|
||||
Configuration: [Configuration – Sniffer Settings](en-Configuration#sniffer-settings-from-v131)
|
||||
|
||||
Complete callsigns and chat-category separation: [Multi-Channel Login](#multi-channel-login-from-v126)
|
||||
|
||||
---
|
||||
|
||||
## Win-Test Integration
|
||||
@@ -319,9 +482,48 @@ Settings: [Win-Test Network Listener](en-Configuration#win-test-network-listener
|
||||
|
||||
## PSTRotator Interface (from v1.31, fully configurable from v1.40)
|
||||
|
||||
KST4Contest can control antenna direction directly via **PSTRotator**. When a station is selected in the user list, the rotator can automatically be turned to the QTF of the selected station.
|
||||
KST4Contest can point the antenna at the selected remote station through the PSTRotator UDP interface. The required azimuth is calculated from the local and remote locators.
|
||||
|
||||
Configuration: [Configuration – PSTRotator Settings](en-Configuration#pstrotator-settings-from-v131)
|
||||
After selecting a station, the **Further Info** section provides the **Turn ant1 to …** button:
|
||||
|
||||

|
||||
|
||||
Pressing the button performs the following steps:
|
||||
|
||||
1. KST4Contest disables PSTRotator tracking mode.
|
||||
2. The QTF of the selected station is transmitted as an integer azimuth.
|
||||
3. PSTRotator controls the configured rotor.
|
||||
4. The reported position becomes the current QTF in KST4Contest.
|
||||
|
||||
The button remains visible while PSTRotator integration is disabled. In that case, no rotator command is sent.
|
||||
|
||||
### Position feedback and SPID compatibility
|
||||
|
||||
KST4Contest requests the current azimuth every two seconds. Position reports update the local QTF field and every function which depends on the antenna direction.
|
||||
|
||||
Some SPID configurations occasionally ignore the first direction command. KST4Contest therefore checks two seconds later whether PSTRotator reported movement or reached the requested target.
|
||||
|
||||
If the position remained unchanged and the target was not reached, KST4Contest sends one compatibility sequence through `0°` followed by the actual target value.
|
||||
|
||||
This check runs in the background. The user interface remains responsive during the two-second interval. A new direction command replaces the pending check belonging to the previous command.
|
||||
|
||||
### What does the reported position confirm?
|
||||
|
||||
The displayed QTF is the azimuth reported by PSTRotator. It confirms that KST4Contest received a usable UDP position message.
|
||||
|
||||
Depending on the station setup, it does not necessarily prove that the antenna is mechanically aligned to exactly that value. This still depends on the controller, calibration, configured offsets and the feedback available to PSTRotator.
|
||||
|
||||
UDP itself provides no delivery acknowledgement. If the displayed QTF does not change, check:
|
||||
|
||||
- that **UDP Control** is enabled in PSTRotator;
|
||||
- that the host and control port match;
|
||||
- that `control port + 1` is available for the position report;
|
||||
- that the firewall permits both UDP directions; and
|
||||
- that PSTRotator itself displays a plausible rotor position.
|
||||
|
||||
In plain terms: KST4Contest provides the target and processes the reported position. The mechanical reality remains the responsibility of the rotor – and occasionally a glance outside.
|
||||
|
||||
Configuration and port assignment: [Configuration – PSTRotator Settings](en-Configuration#pstrotator-settings-from-v131-fully-configurable-from-v140).
|
||||
|
||||
---
|
||||
|
||||
@@ -404,7 +606,7 @@ The score is calculated for the normalised base callsign. A sked entered for an
|
||||
|
||||
### How are multiple suffixes and chat categories handled?
|
||||
|
||||
Active callsigns such as `9A0BB-2`, `9A0BB-70`, `9A0BB-23` and `9A0BB-13` remain separate chat members. Messages can therefore still be addressed to the complete callsign in the correct chat category.
|
||||
Active complete visible callsign variants such as `9A0BB-2`, `9A0BB-70`, `9A0BB-23` and `9A0BB-13` remain separate chat members. Messages can therefore still be addressed to the complete callsign in the correct chat category.
|
||||
|
||||
Worked, band, NOT-QRV and score information belongs to the common base callsign `9A0BB`. The score is calculated once and projected to all active variants. The user list may consequently contain several separate rows with the same score, while the priority list contains only one entry for the base station.
|
||||
|
||||
@@ -443,7 +645,7 @@ In practical terms, the score does not replace the operator’s decision. It pre
|
||||
|
||||
Related settings:
|
||||
|
||||
- [Enabled Bands](en-Configuration#enabled-bands)
|
||||
- [Active Bands](en-Configuration#active-bands)
|
||||
- [Antenna Beamwidth](en-Configuration#antenna-beamwidth)
|
||||
- [Default Maximum QRB](en-Configuration#default-maximum-qrb)
|
||||
- [AirScout Settings](en-Configuration#airscout-settings)
|
||||
@@ -503,13 +705,24 @@ The timeline is a preview. AirScout data can change, and a stored sked guarantee
|
||||
|
||||
## Interval Beacon
|
||||
|
||||
Automatic CQ messages in the public channel at a configurable interval. Recommended: use the `MYQRG` variable so the current frequency is always accurate. Details: [Configuration – Beacon Settings](Configuration#beacon-settings).
|
||||
KST4Contest can send recurring CQ messages to the public chat. The beacon is intended for longer periods of calling CQ on a fixed frequency: it publishes the local QRG regularly without requiring the operator to enter the same text again.
|
||||
|
||||
Both chat categories use one shared interval, but each category has its own enable setting and message template. The second beacon is only sent while the second category is enabled and fully synchronised in the shared ON4KST session.
|
||||
|
||||
Global variables such as `MYQRG`, `SECONDQRG`, `MYLOCATOR` and `MYQTF` are resolved immediately before every transmission. A QRG updated by the logging software can therefore appear in the next beacon.
|
||||
|
||||
Before transmission, KST4Contest validates the completely resolved message. Empty messages, line breaks, the protocol separator `|` and messages exceeding 120 characters are not sent.
|
||||
|
||||
Disable the beacon while searching the band or changing QRG frequently. An automatically published frequency is useful only while somebody is actually listening and calling there.
|
||||
|
||||
Configuration, timer behaviour and available variables: [Configuration – Beacon Settings](en-Configuration#beacon-settings).
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Simplelogfile
|
||||
|
||||
File-based log evaluation using regex. Details: [Log Synchronisation](en-Log-Sync#method-1-universal-file-based-callsign-interpreter-simplelogfile).
|
||||
The Simplelogfile interpreter reads callsigns from a selected text file once per minute. If the file is missing, KST4Contest creates it and displays a non-blocking notice with its path and the initial setup and contest checks. Details: [Log Synchronisation](en-Log-Sync#method-1-universal-file-based-callsign-interpreter-simplelogfile).
|
||||
---
|
||||
|
||||
## Global Message Views
|
||||
@@ -607,7 +820,7 @@ Marker colours provide a compact status indication:
|
||||
|---|---|
|
||||
| Blue | Normal station marker |
|
||||
| Yellow | The callsign has already been worked on at least one band |
|
||||
| Green | The station is inside the current antenna sector and is relevant as a directional candidate |
|
||||
| Green | A directional opportunity has been derived for the station from directed chat messages |
|
||||
| Orange | Currently selected station |
|
||||
|
||||
The selected state has the highest display priority, followed by the directional warning and Worked state. A selected station therefore remains orange even if it also meets one of the other conditions.
|
||||
@@ -780,4 +993,4 @@ The check always uses the **primary screen**. It does not restore the previous p
|
||||
|
||||
The automatic size restriction currently applies to the main window only. The settings window, the separate cluster and QSO monitor window and other auxiliary windows continue to use their stored sizes without the same additional check against the primary screen.
|
||||
|
||||
In plain terms: the protection mainly prevents the central main window from becoming unusable after moving to a smaller display. It is not a complete window-position manager for a changing multi-monitor setup.
|
||||
In plain terms: the protection mainly prevents the central main window from becoming unusable after moving to a smaller display. It is not a complete window-position manager for a changing multi-monitor setup.
|
||||
|
||||
@@ -52,6 +52,8 @@ Downloads, supported operating systems and installation methods are described in
|
||||
|
||||
This manual describes the current stable release of KST4Contest.
|
||||
|
||||
The currently published Stable version is **v1.42.0**.
|
||||
|
||||
Functions that are only available in a Beta or Nightly build are marked accordingly. If no such note is present, the description applies to the stable release.
|
||||
|
||||
- [Download Stable, Beta and Nightly builds](https://kst4contest.hamradioonline.de/download/)
|
||||
@@ -68,6 +70,7 @@ The stable release is normally the appropriate choice for contest operation. Bet
|
||||
|---|---|
|
||||
| [Installation](en-Installation) | ON4KST account, downloads, installation and updates |
|
||||
| [Configuration](en-Configuration) | Login, station, bands, user interface and external connections |
|
||||
| [Contest Workflow](en-Contest-Workflow) | Pre-start checks, CQ operation, candidate selection, skeds, logging and band changes |
|
||||
| [Log Synchronisation](en-Log-Sync) | Simplelogfile, UCXLog, N1MM+, QARTest, DXLog.net and Win-Test |
|
||||
| [AirScout Integration](en-AirScout-Integration) | Connecting AirScout and evaluating aircraft scatter timing |
|
||||
| [DX Cluster Server](en-DX-Cluster-Server) | Passing detected opportunities to logging software |
|
||||
@@ -126,4 +129,4 @@ Special thanks go to:
|
||||
- Philipp (DN9APW) for further development of KST4Contest and the CI/CD infrastructure
|
||||
- all other testers and contributors who supplied reproducible reports, ideas and corrections
|
||||
|
||||
Not every suggestion can be implemented unchanged. Nevertheless, reports from real operation remain an important basis for deciding which problems should be solved first.
|
||||
Not every suggestion can be implemented unchanged. Nevertheless, reports from real operation remain an important basis for deciding which problems should be solved first.
|
||||
|
||||
@@ -267,7 +267,19 @@ Apple Silicon Macs require the package marked `arm64`. Intel Macs require the pa
|
||||
3. Drag `KST4Contest.app` into the **Applications** folder.
|
||||
4. Start KST4Contest from the Applications folder or Launchpad.
|
||||
|
||||
The application is not currently notarized by Apple. macOS may therefore block the first launch.
|
||||
From version 1.42 onwards the macOS packages are signed with an Apple Developer ID and notarized by Apple. The first launch therefore works by double-clicking, without going through the context menu and without a security prompt. The notarization ticket is stapled into the DMG file itself, so the check also succeeds without an internet connection.
|
||||
|
||||
To confirm that a downloaded package really is signed, check it in a terminal:
|
||||
|
||||
```bash
|
||||
spctl --assess --type open --context context:primary-signature -v KST4Contest-v<version>-macos-arm64.dmg
|
||||
```
|
||||
|
||||
The expected result is `accepted` together with `source=Notarized Developer ID`.
|
||||
|
||||
### Versions up to and including 1.41.1
|
||||
|
||||
Older packages are not notarized, so macOS blocks the first launch.
|
||||
|
||||
If the application came from the official GitHub Release:
|
||||
|
||||
@@ -408,7 +420,9 @@ Then start the file from a terminal. Error messages shown there are usually more
|
||||
|
||||
### macOS blocks the application
|
||||
|
||||
Use the **Open** function described under [Installing on macOS](#installing-on-macos). Before doing so, verify that the DMG file came from the official GitHub Release.
|
||||
From version 1.42 onwards this should no longer happen, because the packages are signed and notarized. If it does occur, the DMG file was most likely downloaded incompletely or modified afterwards; download it again from the official GitHub Release.
|
||||
|
||||
On older versions the block is expected. Use the **Open** function described under [Installing on macOS](#installing-on-macos).
|
||||
|
||||
### The problem remains
|
||||
|
||||
|
||||
@@ -10,17 +10,19 @@ KST4Contest imports worked stations from the logging application and derives the
|
||||
|
||||
## Method 1: Universal File Based Callsign Interpreter (Simplelogfile)
|
||||
|
||||
KST4Contest reads a log file and searches it for callsigns using a configurable regular expression. The file is read only and is never modified. Binary log files can also be used; content which cannot be interpreted as text is skipped.
|
||||
KST4Contest reads the selected text file once per minute and searches it for callsigns using a fixed built-in regular expression. Each match is normalised to its base callsign, so the global Worked status applies to every currently active chat variant of that callsign.
|
||||
|
||||
The advantage is broad compatibility: no dedicated network interface is required from the logging application.
|
||||
|
||||
The limitation is equally clear. A callsign match alone provides neither a reliable band nor a locator. The Simplelogfile interpreter can therefore set only the global Worked status. It does not create a per-band `X`, a worked-grid record or a reliable basis for the band-upgrade hint after a log entry.
|
||||
|
||||
Configure the log-file path and regular expression in the **Log sync** tab. Use one of the network interfaces where possible if band-specific information is required.
|
||||
Select the text-file path in the **Log sync** tab. If the file is missing, KST4Contest creates it and displays a one-time, non-blocking notice with the path and the checks to perform next. Read or creation errors are logged; the scheduled task continues with its next one-minute pass. Use one of the network interfaces where possible if band-specific information is required.
|
||||
|
||||
Worked status derived from the Simplelogfile is not stored in the internal SQLite database. The selected file is the durable source and is read again after every restart. The interpreter only adds positive Worked marks; it does not remove an existing mark during the current application session and does not reset automatically for a new contest. A manual database reset does not change or empty the Simplelogfile either. Callsigns contained in it are marked as worked again when the file is next evaluated within one minute. Before each contest, verify that the logging application is writing the current contest log to this exact file.
|
||||
|
||||
---
|
||||
|
||||
# Method 2: Network Listener for QSO UDP Packets – Recommended
|
||||
## Method 2: Network Listener for QSO UDP Packets – Recommended
|
||||
|
||||
UCXLog, QARTest, N1MM+ and DXLog.net can transmit a UDP packet when a QSO is saved. KST4Contest receives these packets on port `12060` by default and imports the callsign together with any band and locator information they contain.
|
||||
|
||||
@@ -164,7 +166,7 @@ In addition to QSO synchronisation, UCXLog and other programs also transmit the
|
||||
|
||||

|
||||
|
||||
**Result**: Your own QRG never needs to be typed manually in the chat – clicking the MYQRG button or using the variable in the beacon is sufficient.
|
||||
**Result**: An enabled interface updates `MYQRG` when it actually supplies valid frequency packets. Enabling an interface does not create a QRG on its own. If no suitable packets arrive, check the interface or disable both automatic sources and maintain the QRG manually.
|
||||
|
||||
**Sources for your own QRG (MYQRG):**
|
||||
- UCXLog, N1MM+, DXLog.net, QARTest via UDP port 12060
|
||||
@@ -192,7 +194,7 @@ For DM5M-style setups (2 radios, 2 computers, one KST4Contest instance or two se
|
||||
|
||||
## Internal Database
|
||||
|
||||
KST4Contest stores Worked, NOT-QRV and worked-grid information in its own SQLite database. This database is independent of the logging application's database.
|
||||
KST4Contest stores Worked, NOT-QRV and worked-grid information received from network interfaces, together with manual marks, in its own SQLite database. This database is independent of the logging application's database. Simplelogfile matches are excluded and are derived again from the selected file in each application session.
|
||||
|
||||
The input sources provide different levels of detail:
|
||||
|
||||
@@ -202,6 +204,6 @@ The input sources provide different levels of detail:
|
||||
| QSO UDP listener | yes | yes, if included in the packet | yes, if both band and locator are available |
|
||||
| Win-Test network listener | yes | yes | yes, if a locator is available |
|
||||
|
||||
The information is restored when KST4Contest starts and updated during operation when new log entries arrive. It expires automatically after three days, so a reset before every contest is normally unnecessary.
|
||||
The information stored in SQLite is restored when KST4Contest starts and updated during operation when new log entries arrive. It expires automatically after three days, so a reset before every contest is normally unnecessary. This lifetime does not apply to the Simplelogfile interpreter: its file remains the durable source and is neither changed nor emptied by a database reset. Callsigns contained in it set the global Worked status again during the next periodic evaluation.
|
||||
|
||||
A complete manual reset removes Worked marks, NOT-QRV marks and worked grid squares together. See [Worked Station Database Settings](en-Configuration#worked-station-database-settings) for details.
|
||||
A complete manual reset removes Worked marks, NOT-QRV marks and worked grid squares together. See [Worked Station Database Settings](en-Configuration#worked-station-database-settings) for details.
|
||||
|
||||
@@ -2,161 +2,371 @@
|
||||
|
||||
> 🇬🇧 You are reading the English version | 🇩🇪 [Deutsche Version](de-Makros-und-Variablen)
|
||||
|
||||
KST4Contest offers a flexible system of text snippets, shortcuts and built-in variables that significantly speed up the chat workflow during contests.
|
||||
KST4Contest distinguishes between shortcut buttons, text snippets and variables. Shortcuts and snippets contain prepared text. Variables add information which may change during operation.
|
||||
|
||||
Inserted text remains visible in the send field and can be checked or edited before transmission.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
| Type | Access | Purpose |
|
||||
| Mechanism | Access | Use |
|
||||
|---|---|---|
|
||||
| **Shortcuts** | Button in the toolbar | Quick text insert into the send field |
|
||||
| **Snippets** | Right-click / Ctrl+1..0 | Text building blocks, optional PM sending |
|
||||
| **Variables** | Usable in all text fields | Dynamic values (QRG, locator, AP data) |
|
||||
| **Shortcut** | Button above the send field | Inserts configured text into the send field |
|
||||
| **Snippet** | Context menu or `Ctrl+1` through `Ctrl+0` | Prepares text for the selected station |
|
||||
| **Variable** | Placeholder within message text | Inserts current QRG, locator, direction, station or AirScout information |
|
||||
|
||||
Shortcuts and snippets store text. Variables supply the corresponding current values.
|
||||
|
||||
A shortcut such as
|
||||
|
||||
```text
|
||||
pse sked?
|
||||
```
|
||||
|
||||
always inserts the same text. A shortcut containing
|
||||
|
||||
```text
|
||||
pse call me at MYQRGSHORT
|
||||
```
|
||||
|
||||
instead uses the QRG stored in KST4Contest when the button is pressed.
|
||||
|
||||
---
|
||||
|
||||
## Shortcuts (Quick-Access Buttons)
|
||||
## Shortcut Buttons
|
||||
|
||||
Configurable in Preferences → **Shortcut Settings**.
|
||||
Shortcuts are configured under **Preferences → Shortcut Settings**.
|
||||
|
||||
- Each configured text creates **one button** in the user interface.
|
||||
- Clicking a button inserts the text into the **send field**.
|
||||
- **All variables** can be used in shortcuts and are resolved immediately when inserted.
|
||||
- Longer texts are also possible.
|
||||

|
||||
|
||||
**Tip**: Set up frequently used abbreviations like "pse", "rrr", "tnx", "73" as shortcuts.
|
||||
Each entry creates one button in the main window. Pressing it appends the configured text to the existing contents of the send field. Text which has already been prepared is not removed.
|
||||
|
||||
If a shortcut contains a variable, the variable is resolved when the text is inserted. For example,
|
||||
|
||||
```text
|
||||
pse call me at MYQRGSHORT
|
||||
```
|
||||
|
||||
may become:
|
||||
|
||||
```text
|
||||
pse call me at 144.388
|
||||
```
|
||||
|
||||
The exact entries `MYQRG` and `SECONDQRG` are highlighted as QRG buttons. They insert the current QRG of the first or second chat category respectively.
|
||||
|
||||
The shortcut
|
||||
|
||||
```text
|
||||
/SETNAME MYQRG
|
||||
```
|
||||
|
||||
is highlighted as well. Pressing it resolves `MYQRG` and inserts the resulting server command into the send field. The command is not transmitted automatically.
|
||||
|
||||
The order of the entries in the settings determines the button order in the main window. Editing, sorting and saving are described under [Configuration – Shortcut Settings](en-Configuration#shortcut-settings).
|
||||
|
||||
---
|
||||
|
||||
## Snippets (Text Building Blocks)
|
||||
## Text Snippets
|
||||
|
||||
Configurable in Preferences → **Snippet Settings**.
|
||||
Snippets are configured under **Preferences → Snippet Settings**. They are intended primarily for recurring messages to a particular station.
|
||||
|
||||
### Access
|
||||
Snippets can be opened:
|
||||
|
||||
- **Right-click** on a callsign in the user list
|
||||
- **Right-click** in the CQ message table
|
||||
- **Right-click** in the PM message table
|
||||
- **Keyboard shortcuts**: `Ctrl+1` to `Ctrl+0` for the first 10 snippets
|
||||
- by right-clicking a station in the user list,
|
||||
- by right-clicking a public message,
|
||||
- by right-clicking a private message, or
|
||||
- with `Ctrl+1` through `Ctrl+0` for the first ten entries in the snippet list.
|
||||
|
||||
### Behaviour with a Selected Callsign
|
||||
### Using the Context Menu
|
||||
|
||||
When a callsign is selected in the user list, the snippet is addressed as a **private message**:
|
||||
Selecting a station or message will normally have prepared the corresponding `/cq` destination in the send field. A snippet subsequently chosen from the context menu is appended to this text.
|
||||
|
||||
```
|
||||
/CQ CALLSIGN <snippet text>
|
||||
Existing message text can therefore be extended deliberately.
|
||||
|
||||
### Using the Keyboard
|
||||
|
||||
A snippet invoked with `Ctrl+1` through `Ctrl+0` replaces the previous contents of the send field with a complete directed message:
|
||||
|
||||
```text
|
||||
/cq CALLSIGN snippet text
|
||||
```
|
||||
|
||||
Then **Enter** can be pressed to send directly – even if the send field does not have focus.
|
||||
The complete visible callsign, including any suffix, is retained. Selecting `9A0BB-70` may therefore produce:
|
||||
|
||||
### Hardware Macro Keyboard
|
||||
```text
|
||||
/cq 9A0BB-70 pse ur qrg?
|
||||
```
|
||||
|
||||
*(Idea by IU3OAR, Gianluca Costantino)*
|
||||
KST4Contest also retains the selected station's chat category internally. A snippet for `9A0BB-70` is therefore not accidentally transmitted through the other active chat category.
|
||||
|
||||
The key combinations `Ctrl+1` to `Ctrl+0` can be assigned to a programmable macro keyboard. One key press triggers the snippet, another press (mapped to Enter) sends it immediately. In contest operation this saves considerable time.
|
||||
If no station is selected, or no snippet exists for the selected key combination, nothing is inserted.
|
||||
|
||||
### Predefined Default Snippets
|
||||
The prepared text is not sent automatically:
|
||||
|
||||
On first start, some snippets are pre-configured, e.g.:
|
||||
- `Enter` or **TX** sends the message.
|
||||
- `Esc` clears the send field.
|
||||
|
||||
- `Hi OM, try sked?`
|
||||
- `I am calling cq ur dir, pse lsn to me at MYQRG`
|
||||
- `pse ur qrg?`
|
||||
- `rrr, I move to your qrg nw, pse ant dir me`
|
||||
### Keyboard Mapping
|
||||
|
||||
These can be customised or deleted in the Preferences.
|
||||
The mapping follows the order of the snippet list:
|
||||
|
||||
| Key combination | Entry used |
|
||||
|---|---:|
|
||||
| `Ctrl+1` | first entry |
|
||||
| `Ctrl+2` | second entry |
|
||||
| … | … |
|
||||
| `Ctrl+9` | ninth entry |
|
||||
| `Ctrl+0` | tenth entry |
|
||||
|
||||
The key combinations can also be assigned to a programmable macro keyboard. The idea for this method came from IU3OAR, Gianluca Costantino.
|
||||
|
||||
KST4Contest does not define a mandatory set of default snippets. The useful texts depend on the station's own contest operation and operating method.
|
||||
|
||||
Editing, sorting and saving are described under [Configuration – Snippet Settings](en-Configuration#snippet-settings).
|
||||
|
||||
---
|
||||
|
||||
## Variables
|
||||
|
||||
Variables in written texts (snippets, shortcuts, beacon, send field) are replaced by their current values at runtime. Simply type the variable name in **uppercase** in the text.
|
||||
Variables are reserved placeholders within message text. They must be written in uppercase and are case-sensitive.
|
||||
|
||||
### MYQRG
|
||||
Variables can be used in:
|
||||
|
||||
Replaced by the current transceiver frequency.
|
||||
- shortcuts,
|
||||
- snippets,
|
||||
- beacon texts, and
|
||||
- message text entered or pasted directly into the send field.
|
||||
|
||||
- Source: TRX sync via UDP from the logging software (if enabled)
|
||||
- Fallback: Manually entered value in the MYQRG text field to the right of the send button
|
||||
- Format: `144.388.03`
|
||||
Variables in a shortcut or snippet are resolved when the text is inserted into the send field. Variables entered or pasted directly into the send field are resolved immediately before the message is placed in the transmission queue.
|
||||
|
||||
**Example**: `calling cq at MYQRG` → `calling cq at 144.388.03`
|
||||
Station-specific variables always use the currently selected station. KST4Contest does not derive this station from a `/cq` destination entered manually in the message text.
|
||||
|
||||
### MYQRGSHORT
|
||||
---
|
||||
|
||||
Like MYQRG, but only the first 7 characters.
|
||||
## Global Variables
|
||||
|
||||
- Format: `144.388`
|
||||
Global variables do not require a selected remote station.
|
||||
|
||||
**Example**: `qrg: MYQRGSHORT` → `qrg: 144.388`
|
||||
| Variable | Replacement value |
|
||||
|---|---|
|
||||
| `MYQRG` | current QRG of the first or primary chat category |
|
||||
| `MYQRGSHORT` | first seven characters of `MYQRG` |
|
||||
| `SECONDQRG` | current QRG of the second chat category |
|
||||
| `MYLOCATOR` | complete locator configured for the local station |
|
||||
| `MYLOCATORSHORT` | first four characters of the local locator |
|
||||
| `MYCALL` | configured local callsign |
|
||||
| `MYQTF` | current antenna direction as a numeric value in degrees |
|
||||
|
||||
### MYLOCATOR
|
||||
For example,
|
||||
|
||||
Replaced by your own Maidenhead locator (6 characters).
|
||||
```text
|
||||
cq at MYQRGSHORT, qtf MYQTF, loc MYLOCATOR
|
||||
```
|
||||
|
||||
- Format: `JO51IJ`
|
||||
may be resolved to:
|
||||
|
||||
**Example**: `my loc: MYLOCATOR` → `my loc: JO51IJ`
|
||||
```text
|
||||
cq at 144.388, qtf 135, loc JO51IJ
|
||||
```
|
||||
|
||||
### MYLOCATORSHORT
|
||||
### QRG Variables
|
||||
|
||||
Like MYLOCATOR, but only the first 4 characters.
|
||||
`MYQRG` contains the QRG of the first chat category. The value may come from TRX synchronisation with the logging software or from the manually edited QRG field.
|
||||
|
||||
- Format: `JO51`
|
||||
`MYQRGSHORT` uses the same value, but limits it to the first seven characters:
|
||||
|
||||
**Example**: `loc: MYLOCATORSHORT` → `loc: JO51`
|
||||
```text
|
||||
144.388.03 → 144.388
|
||||
```
|
||||
|
||||
`SECONDQRG` contains the QRG of the second chat category. Selecting a station from the second chat does not change the meaning of `MYQRG`. Use `SECONDQRG` explicitly when the QRG of the second category is required.
|
||||
|
||||
### Locator Variables
|
||||
|
||||
`MYLOCATOR` inserts the complete configured locator of the local station:
|
||||
|
||||
```text
|
||||
JO51IJ
|
||||
```
|
||||
|
||||
`MYLOCATORSHORT` uses only the first four characters:
|
||||
|
||||
```text
|
||||
JO51
|
||||
```
|
||||
|
||||
### MYQTF
|
||||
|
||||
`MYQTF` inserts the current antenna direction stored in KST4Contest as a numeric angle in degrees.
|
||||
|
||||
For example,
|
||||
|
||||
```text
|
||||
ant MYQTF deg
|
||||
```
|
||||
|
||||
may become:
|
||||
|
||||
```text
|
||||
ant 135 deg
|
||||
```
|
||||
|
||||
The direction is not converted into compass terms such as `north`, `north-east` or `south-west`.
|
||||
|
||||
---
|
||||
|
||||
## Variables for the Selected Station
|
||||
|
||||
These variables require a selected remote station:
|
||||
|
||||
| Variable | Replacement value |
|
||||
|---|---|
|
||||
| `QRZNAME` | name of the selected station, or its complete callsign if no name is available |
|
||||
| `FIRSTAP` | description and arrival time of the first aircraft reported by AirScout |
|
||||
| `SECONDAP` | description and arrival time of the second aircraft reported by AirScout |
|
||||
|
||||
For example,
|
||||
|
||||
```text
|
||||
Hi QRZNAME, FIRSTAP, pse lsn at MYQRGSHORT
|
||||
```
|
||||
|
||||
may become:
|
||||
|
||||
```text
|
||||
Hi David, a very big AP in 2 min, pse lsn at 144.388
|
||||
```
|
||||
|
||||
### QRZNAME
|
||||
|
||||
Replaced by the **name** of the currently selected station from the chat name field.
|
||||
|
||||
**Example**: `Hi QRZNAME, sked?` → `Hi Gianluca, sked?`
|
||||
KST4Contest uses the name from the selected station's name field. If that field does not contain a usable name, the complete visible callsign is inserted instead.
|
||||
|
||||
### FIRSTAP
|
||||
|
||||
Replaced by data of the first reflectable aircraft to the selected station (if available).
|
||||
If an AirScout candidate is available, `FIRSTAP` contains its description and the expected time until the reflection window.
|
||||
|
||||
- Condition: AirScout is active and an aircraft is available.
|
||||
- Example format: `a very big AP in 1 min`
|
||||
For example:
|
||||
|
||||
**Example**: `AP info: FIRSTAP` → `AP info: a very big AP in 1 min`
|
||||
```text
|
||||
a very big AP in 2 min
|
||||
```
|
||||
|
||||
If no aircraft is available for the selected station, KST4Contest inserts:
|
||||
|
||||
```text
|
||||
no ap available
|
||||
```
|
||||
|
||||
### SECONDAP
|
||||
|
||||
Like FIRSTAP, but for the second available aircraft.
|
||||
`SECONDAP` uses the second available AirScout candidate.
|
||||
|
||||
- Example format: `Next big AP in 9 min`
|
||||
For example:
|
||||
|
||||
**Example**: `also: SECONDAP` → `also: Next big AP in 9 min`
|
||||
```text
|
||||
Next big AP in 9 min
|
||||
```
|
||||
|
||||
### MYQTF *(planned for v1.3)*
|
||||
If there is no second candidate, `SECONDAP` is replaced with an empty string.
|
||||
|
||||
Replaced by the current antenna direction in words (e.g. `north`, `north east`, `east`, …).
|
||||
Further information about the aircraft data is available under [AirScout Integration](en-AirScout-Integration#ap-variables-in-messages).
|
||||
|
||||
- Source: Degree value in the MYQTF input field (to the right of the MYQRG field)
|
||||
### Behaviour Without a Selected Station
|
||||
|
||||
If no station is selected, `QRZNAME`, `FIRSTAP` and `SECONDAP` remain visible in the text. KST4Contest does not remove these placeholders automatically.
|
||||
|
||||
A visible unresolved placeholder is clearer than a formally complete message which silently lacks important information. Before transmission, check that the intended station is selected and that all required variables have been resolved.
|
||||
|
||||
---
|
||||
|
||||
## Variables in the Beacon
|
||||
|
||||
All variables can also be used in the **automatic beacon** (interval messages). Recommended beacon configuration:
|
||||
A public beacon has no selected remote station. It can therefore use only global variables:
|
||||
|
||||
```
|
||||
calling cq at MYQRG, loc MYLOCATOR, GL all!
|
||||
- `MYQRG`
|
||||
- `MYQRGSHORT`
|
||||
- `SECONDQRG`
|
||||
- `MYLOCATOR`
|
||||
- `MYLOCATORSHORT`
|
||||
- `MYCALL`
|
||||
- `MYQTF`
|
||||
|
||||
`QRZNAME`, `FIRSTAP` and `SECONDAP` are not resolved in a beacon and should not be used there.
|
||||
|
||||
A possible template for the first chat category is:
|
||||
|
||||
```text
|
||||
calling cq at MYQRGSHORT, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Since KST4Contest automatically reads QRG data from chat messages: if other stations also use KST4Contest, they will immediately see your QRG in the QRG column of their user list.
|
||||
If the second chat category uses a different QRG, its template must contain `SECONDQRG`:
|
||||
|
||||
```text
|
||||
calling cq at SECONDQRG, ant MYQTF deg, loc MYLOCATOR
|
||||
```
|
||||
|
||||
Global variables are evaluated again on every timer run. A QRG updated by the logging software can therefore already appear in the next beacon message.
|
||||
|
||||
The fully resolved beacon text:
|
||||
|
||||
- must contain at least one valid character,
|
||||
- must not exceed 120 characters,
|
||||
- must not contain the protocol separator `|`, and
|
||||
- must not contain line breaks.
|
||||
|
||||
If the text is empty or invalid when transmission is due, that beacon run is skipped.
|
||||
|
||||
The interval, activation and behaviour of both categories are described under [Configuration – Beacon Settings](en-Configuration#beacon-settings).
|
||||
|
||||
---
|
||||
|
||||
## Example Contest Workflow with Macros
|
||||
## Example Snippet Workflow
|
||||
|
||||
1. Select a station in the user list → callsign is now pre-selected.
|
||||
2. Press `Ctrl+1` → Snippet "Hi OM, try sked?" is addressed as a PM.
|
||||
3. Press Enter → Message sent.
|
||||
4. Station replies with frequency → QRG column is automatically filled.
|
||||
5. Press `Ctrl+2` → Snippet "I am calling cq ur dir, pse lsn to me at 144.388" (MYQRG resolved).
|
||||
6. Press Enter → Sent.
|
||||
For example, the first configured snippet may contain:
|
||||
|
||||
No manual typing, no errors, no interruption to CQ calling.
|
||||
```text
|
||||
Hi QRZNAME, pse sked? I call at MYQRGSHORT
|
||||
```
|
||||
|
||||
The workflow can then look like this:
|
||||
|
||||
1. Select `DL1ABC-432` in the user list.
|
||||
2. Press `Ctrl+1`.
|
||||
3. KST4Contest prepares the directed message and resolves its variables.
|
||||
4. Check the complete text in the send field.
|
||||
5. If the remote station has proposed another QRG, edit the text accordingly.
|
||||
6. Press `Enter` or **TX** to send the message.
|
||||
|
||||
The result may be:
|
||||
|
||||
```text
|
||||
/cq DL1ABC-432 Hi Peter, pse sked? I call at 432.088
|
||||
```
|
||||
|
||||
The complete callsign determines the recipient. The selected chat category determines the transmission path. Variables reduce repeated typing, but they do not decide whether the inserted information still matches the current operating situation.
|
||||
|
||||
---
|
||||
|
||||
## Limits of Variable Resolution
|
||||
|
||||
Variables reflect the information available to KST4Contest at the time they are resolved.
|
||||
|
||||
In particular:
|
||||
|
||||
- A QRG supplied by the logging software may have changed in the meantime.
|
||||
- A manually entered QRG remains active until it is changed again.
|
||||
- `MYQRG` remains the QRG of the primary category even if a station from the second category is selected.
|
||||
- The selected station may differ from a `/cq` destination entered manually.
|
||||
- AirScout may not provide current aircraft data for the path in question.
|
||||
- Station-specific variables remain visible when no station is selected.
|
||||
- Inserted text is not checked automatically for operational correctness.
|
||||
|
||||
The send field therefore remains editable after a shortcut or snippet has been inserted. Variables avoid repeated input; the final check remains the operator's responsibility.
|
||||
|
||||
---
|
||||
|
||||
## Use During a Contest
|
||||
|
||||
Shortcuts, snippets and variables are individual tools within the operating workflow. Their interaction with CQ operation, station selection, skeds, QRG changes and log synchronisation is described under [Contest Workflow with KST4Contest](en-Contest-Workflow).
|
||||
|
||||
@@ -4,21 +4,69 @@
|
||||
|
||||
## Connecting to the Chat
|
||||
|
||||
1. Select a **chat category** in the settings window (e.g. 144 MHz VHF, 432 MHz UHF, …).
|
||||
2. Click the **Connect** button.
|
||||
3. Wait for the connection to be established.
|
||||
Before connecting for the first time, configure the local login callsign and password, the locator shared by the TCP session, and the primary chat category in the settings window. If a second category is required, enable and select it there. KST4Contest signs in once and adds the second category through Single Sign-on within the same TCP session.
|
||||
|
||||
> Disconnecting and reconnecting is only possible via the settings window. It is therefore recommended to keep the settings window open.
|
||||
The connection can be started in two ways:
|
||||
|
||||
- **Connect to …** in the settings window applies the values currently entered there and starts the connection.
|
||||
- **File → Connect to …** uses the settings already applied in KST4Contest.
|
||||
|
||||
Use **Save Settings** if changed values should also be available after the next programme start.
|
||||
|
||||
An active connection can be terminated using **File → Disconnect** or **Disconnect** in the settings window. **Exit + disconnect** terminates the connection and then closes the programme.
|
||||
|
||||
If an established connection is lost unexpectedly, KST4Contest waits for a limited period and then attempts a controlled reconnect to ON4KST. A failed initial connection attempt no longer blocks the user interface.
|
||||
|
||||
The [`LINK` indicator](#status-bar-and-indicators) in the main window shows whether only the TCP connection exists or whether login and synchronisation have actually been completed.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Main Window Overview
|
||||
|
||||
The main window consists of several areas:
|
||||
|
||||
### Status Bar and Indicators
|
||||
|
||||
The status bar is located at the top of the main window next to the menu.
|
||||
|
||||

|
||||
|
||||
The permanently visible `LINK` indicator shows the actual state of the ON4KST connection:
|
||||
|
||||
| Indicator | Meaning |
|
||||
|---|---|
|
||||
| green `LINK` | Login and synchronisation of all configured chat categories have been completed |
|
||||
| yellow `LINK…` | Connection, login, user-list synchronisation or controlled shutdown is in progress |
|
||||
| red `LINK!` | No connection exists, or KST4Contest is waiting before an automatic reconnect |
|
||||
|
||||
The tooltip contains the internal connection state and a more detailed description of the current step. The indicator is not a button.
|
||||
|
||||
KST4Contest reports `ONLINE` only after login has been confirmed and the user lists of all configured categories have been received. The send field and **TX** remain disabled while the connection is still being established or resynchronised.
|
||||
|
||||
Additional indicators appear temporarily after certain events:
|
||||
|
||||
- `SKED` indicates that a sked reminder is due. The text contains the complete target callsign and the remaining time.
|
||||
- `BAND+` appears after a log entry if at least one common, locally enabled and unworked band has been detected for the worked station.
|
||||
|
||||
Both indicators flash for approximately twelve seconds and then disappear. Their tooltip contains the complete message or derivation. Neither indicator is clickable.
|
||||
|
||||
|
||||
### PM Window (top left)
|
||||
|
||||
Shows all received **private messages** as well as intercepted public messages containing your own callsign. New messages appear in **red** and fade every 30 seconds from yellow to white.
|
||||
The PM window shows private messages addressed to the local login callsign and the corresponding outgoing replies.
|
||||
|
||||
Messages not sent by the local station are also shown when their text contains the configured local login callsign, ignoring letter case. This applies to public messages addressed to `ALL` and to directed messages between other chat participants. The informal description **“gossip detection”** is particularly apt for the latter case.
|
||||
|
||||
The actual receiver, message text, chat category and routing remain unchanged. The message merely gains an additional representation in the PM window.
|
||||
|
||||
If [QSO Monitoring](en-Features#qso-sniffer-from-v131) is enabled, it additionally shows captured messages involving the monitored base callsigns. These entries receive a `Sniffed:` prefix containing the complete visible sender and receiver callsigns.
|
||||
|
||||
New rows not sent by the local station pass through six green age levels and return to the normal table colour after five minutes. Messages sent by the local station retain their separate highlight. The colour only indicates message age; it does not change content or routing.
|
||||
|
||||
Selecting an incoming row prepares a reply to the sender. For an outgoing message from the local station, the original receiver is restored as the message target instead. Caught and monitored rows do not trigger PM audio output.
|
||||
|
||||
Age levels: [Coloured PM Rows](en-Features#coloured-pm-rows-from-v125). Recognition and limitations: [PM Catching](en-Features#pm-catching-from-v11).
|
||||
|
||||
### User List (Chat Members)
|
||||
|
||||
@@ -40,6 +88,11 @@ The central table of all currently active chat users. Columns (depending on conf
|
||||
| NOT QRV @ | Bands on which the station has manually been marked not QRV |
|
||||
| Category | Chat category of this entry |
|
||||
|
||||
The QRG column shows the frequency most recently detected for a station. Missing trailing zeros are added for display purposes, so `144.21`, for example, is shown as `144.210`. If KST4Contest detects frequencies on several bands in succession, the column shows the latest match. The internal band information may still contain several current bands for that station.
|
||||
|
||||
Relative frequency information is first combined with a band context from the same sender which is no more than 30 minutes old. Only if no such context exists does KST4Contest use the global fallback band. Detection rules, examples and limitations: [QRG Detection](en-Features#qrg-detection).
|
||||
|
||||
|
||||
### Worked, band and grid-square status
|
||||
|
||||
The subcolumns under **worked** use compact codes because several enabled bands leave little room for full descriptions. `X` marks a callsign worked on that band. `a` and `B+` identify an offered band which has not yet been worked. An appended `o` means that the four-character grid square has already been worked on this band.
|
||||
@@ -52,39 +105,139 @@ Each status cell has a tooltip containing the legend and the state derived for t
|
||||
|
||||
**Sorting**: Click column headers. QRB sorting is numerical (corrected in v1.22).
|
||||
|
||||
A callsign displayed in green and bold indicates a directional opportunity derived from a directed message. The marker applies to the sender of that message and remains visible for no more than five minutes. Derivation and limitations: [Directional Opportunities from Directed Messages](en-Features#directional-opportunities-from-directed-messages).
|
||||
|
||||
|
||||
### Send Field
|
||||
|
||||
Text input for outgoing messages. After clicking a callsign in the user list, the send field automatically receives focus – start typing immediately without double-clicking (from v1.22).
|
||||
The send field contains the prepared text for the next outgoing message.
|
||||
|
||||
### MYQRG Field
|
||||
When an operator deliberately selects a station in the user list using the mouse or keyboard, KST4Contest prepares a directed message:
|
||||
|
||||
To the right of the send button. Shows the current own QRG, can also be entered manually.
|
||||
```text
|
||||
/cq CALLSIGN
|
||||
```
|
||||
|
||||
### MYQTF Field *(for v1.3)*
|
||||
The complete visible callsign, including any suffix, and the chat category of the selected station are retained. A target such as `9A0BB-70` is not shortened to `9A0BB`.
|
||||
|
||||
Input field for the current antenna direction. Used for the planned `MYQTF` variable.
|
||||
A background refresh, changed sorting order or filter update must not overwrite message text which has already been edited. Only an actual station selection by the operator prepares the `/cq` recipient again.
|
||||
|
||||
- **TX** or `Enter` sends the prepared text.
|
||||
- `Esc` clears the send field.
|
||||
- The send field and **TX** remain disabled until KST4Contest is fully connected to ON4KST.
|
||||
|
||||
Shortcuts, snippets and variables are described under [Macros and Variables](en-Macros-and-Variables).
|
||||
|
||||
### MYQRG and SECONDQRG Fields
|
||||
|
||||
The two QRG fields contain the local frequencies for the primary and secondary chat categories.
|
||||
|
||||
`MYQRG` can be updated by an enabled TRX synchronisation interface or entered manually when no automatic QRG source is active. `SECONDQRG` remains independent and contains the frequency used for the second category.
|
||||
|
||||
Selecting a station from the second chat does not change the meaning of these values: `MYQRG` continues to belong to the primary category and `SECONDQRG` to the secondary category.
|
||||
|
||||
Further details: [TRX Sync Settings](en-Configuration#trx-sync-settings).
|
||||
|
||||
### MYQTF Field
|
||||
|
||||
The MYQTF field shows the current antenna direction as a numerical angle in degrees.
|
||||
|
||||
If PSTRotator is enabled, the value is received automatically and the field cannot be edited manually. Without active rotator synchronisation, the antenna direction can be entered directly. The changed value is applied when the field loses focus.
|
||||
|
||||
The value affects, among other things:
|
||||
|
||||
- QTF filtering,
|
||||
- the antenna-sector display on the station map,
|
||||
- priority-score calculation,
|
||||
- the AP timeline, and
|
||||
- the `MYQTF` variable.
|
||||
|
||||
---
|
||||
|
||||
## Filters
|
||||
## Message Tables
|
||||
|
||||
The filter bar is located above the chat-member table and groups related controls:
|
||||
KST4Contest deliberately displays message text on a single line. This keeps a larger number of entries visible when chat activity is high. The disadvantage is obvious: if the **Message** column is narrow, not every message fits completely into its cell.
|
||||
|
||||
- **Show only QTF** limits the list to a selected antenna direction.
|
||||
- **Show only QRB [km] <=** sets a maximum distance.
|
||||
- **Find** searches for a callsign.
|
||||
- **wkd** hides callsigns which have already been worked on at least one band.
|
||||
- The individual band buttons hide a station if it has already been worked on that band or has been marked NOT QRV there. Only bands enabled for the local station are shown.
|
||||
- **Only new grids** shows only stations in four-character grid squares which have not been worked on any band.
|
||||
- **Grid color** is not a filter. It marks the QRA cell of an already worked grid square without hiding stations.
|
||||
- **New bands** shows stations with at least one detected, locally enabled and unworked band opportunity. NOT-QRV marks take precedence.
|
||||
- **Reachability**, **Tropo >=0dB** and **AS next 5m** limit the list according to the selected path or AirScout criteria.
|
||||
If the message text is wider than the visible cell, moving the mouse over that **Message** cell displays the complete content in a tooltip. No additional full-text tooltip is shown if the message already fits into the column.
|
||||
|
||||
The filter bar has no fixed width. QTF, Worked and Reachability controls initially use the available space in their respective rows. When the horizontal divider is moved to the right and the chat-member area becomes narrower, controls wrap only when their actual required width no longer fits.
|
||||
Web addresses beginning with `http://`, `https://` or `www.` are displayed as links inside the message text. Clicking a link opens it in the operating system’s default browser. Other protocols are not treated as links.
|
||||
|
||||

|
||||
|
||||
This avoids having to move the divider merely to read an individual long message. The divider can, of course, still be adjusted if a permanently wider message area is required.
|
||||
|
||||
---
|
||||
|
||||
## Filters and Reachability Controls
|
||||
|
||||
The filter bar is located above the chat-member table. Filters can be combined; a station remains visible only if it satisfies every active condition.
|
||||
|
||||

|
||||
|
||||
In plain terms: the filters determine the table contents, but no longer enforce the minimum width of the entire right-hand side. The bar remains compact in the normal layout and uses additional height only when the view becomes genuinely narrow. Moving the divider back to the left immediately returns the controls to the available rows.
|
||||
### Station Filters
|
||||
|
||||
| Control | Effect |
|
||||
|---|---|
|
||||
| **Show only QTF** | Shows only stations inside the selected antenna direction and configured beamwidth |
|
||||
| **Show only QRB [km] <=** | Limits the list to the entered maximum distance |
|
||||
| **Find** | Filters by a complete or partial callsign |
|
||||
| **wkd** | Hides base callsigns already worked on at least one supported band |
|
||||
| individual band buttons | Hide stations already worked on that band or marked NOT QRV there |
|
||||
| **Inactive stations** | Hides stations whose latest chat activity was more than 20 minutes ago |
|
||||
| **Only new grids** | Shows only stations in four-character grid squares not yet worked on any band |
|
||||
| **New bands** | Shows stations with at least one detected, locally enabled and unworked band opportunity |
|
||||
| **Tropo >=0dB** | Shows stations with a calculated non-negative SSB margin |
|
||||
| **AS next 5m** | Shows stations with a current AirScout window or one expected within the next five minutes |
|
||||
|
||||
For **New bands**, KST4Contest evaluates current QRGs, band information in the name field and active callsign variants together. Manual NOT-QRV marks take precedence.
|
||||
|
||||
The **Tropo >=0dB** filter removes only stations for which a completed calculation returned a negative margin. Stations with pending or failed calculations remain visible. Otherwise, a missing API result would incorrectly be treated as proof that the path is unsuitable.
|
||||
|
||||
### Grid Color
|
||||
|
||||
**Grid color** is not a filter. It only changes the presentation of the QRA cell and marks four-character grid squares which have already been worked.
|
||||
|
||||
The station remains visible regardless of this colour marker. **Reset filters** therefore does not disable **Grid color**.
|
||||
|
||||
### Reachability and Calc Selected
|
||||
|
||||
The **Reachability** dropdown selects the band used by the Tropo column, the Tropo filter and an explicitly requested path calculation.
|
||||
|
||||
- **Auto** derives the band from the station’s current QRG, band information in its name field and the supported chat category.
|
||||
- An explicitly selected band overrides this automatic choice for the Reachability calculation.
|
||||
|
||||
Changing the dropdown does not start a calculation for the entire user list. With an online elevation-data source, that would be unnecessarily slow and multiply the number of external API requests.
|
||||
|
||||
**Calc selected** calculates only the currently selected station, using either the explicitly selected or automatically derived band. The result is then used by the Tropo column and the associated views.
|
||||
|
||||
### Resetting the Filters
|
||||
|
||||
**Reset filters** clears:
|
||||
|
||||
- the QTF filter,
|
||||
- the QRB filter,
|
||||
- the callsign search field,
|
||||
- all Worked and band filters,
|
||||
- **Inactive stations**,
|
||||
- **Only new grids**,
|
||||
- **New bands**,
|
||||
- **Tropo >=0dB**, and
|
||||
- **AS next 5m**.
|
||||
|
||||
The internal filter predicates are explicitly cleared as well. Resetting only the visible toggle buttons would not be sufficient.
|
||||
|
||||
The following settings are retained:
|
||||
|
||||
- **Grid color**, because it is a display option, and
|
||||
- the **Reachability** selection, because it selects the calculation band rather than directly filtering the table.
|
||||
|
||||
### Behaviour in a Narrow View
|
||||
|
||||
The filter bar has no fixed width. QTF, Worked and Reachability controls initially use the available space in their respective rows.
|
||||
|
||||
When the middle divider is moved to the right and the chat-member area becomes narrower, controls wrap only when their actual required width no longer fits. Widening the area causes them to rearrange immediately.
|
||||
|
||||
In plain terms: the filters determine the table contents, but no longer enforce the minimum width of the entire right-hand side.
|
||||
|
||||
---
|
||||
|
||||
@@ -158,99 +311,171 @@ Calculation and limitations: [Priority Score and Priority List](en-Features#prio
|
||||
|
||||
## Station Map
|
||||
|
||||
The station map is opened or closed through:
|
||||
The station map can be opened in two ways:
|
||||
|
||||
**Windows → Show / hide station map**
|
||||
- **Windows → Show / hide station map** opens or closes the map window.
|
||||
- **Show on map** in the **Further Info** panel opens the map and focuses the selected station.
|
||||
|
||||
The window uses the chat members currently visible in the filtered user list. Changing the QRB, QTF, Worked, band or Reachability filters can therefore also change the stations shown on the map.
|
||||
The map uses the stations which remain visible after applying the current user-list filters. Its header shows the number of displayed stations and indicates a filtered view with `filtered view active`.
|
||||
|
||||
A station can additionally be opened directly from the **Further Info** panel using **Show on map**. This selects the station on the map and requests the associated path analysis.
|
||||

|
||||
|
||||
Stations with the same normalised base callsign and position are combined into one marker. At lower zoom levels, nearby markers may additionally be displayed as clusters. These are display groups only; the individual chat logins remain separate message targets inside KST4Contest.
|
||||
### Selecting a Station
|
||||
|
||||
Clicking a station marker:
|
||||
A single station marker can be selected directly. KST4Contest then:
|
||||
|
||||
1. selects the corresponding chat member,
|
||||
2. scrolls the main user list to that entry,
|
||||
3. updates the **Further Info** panel, and
|
||||
4. prepares the complete visible callsign as the message target.
|
||||
4. prepares the complete visible callsign as the `/cq` recipient.
|
||||
|
||||
The map details for the selected station include its locator, QRB, QTF, detected bands and available band opportunities. **Trigger cluster spot** sends a spot through the built-in local DX Cluster server so that connected logging software can receive the selected station and QRG.
|
||||
Chat logins with the same normalised base callsign and position may share one marker. They nevertheless remain separate message targets inside KST4Contest.
|
||||
|
||||
The path-analysis section shows the terrain profile and the calculated route between both stations. Depending on the available data, it includes:
|
||||
Markers which are too close together at the current zoom level are displayed as a cluster containing the number of stations. Clicking the cluster zooms into that area. A concrete station is selected only after an individual marker becomes visible and is clicked.
|
||||
|
||||
For a selected station, the header additionally shows:
|
||||
|
||||
- the complete callsign,
|
||||
- locator,
|
||||
- QRB and QTF,
|
||||
- detected active bands,
|
||||
- any available `B+` band opportunity, and
|
||||
- the most recently known QRGs.
|
||||
|
||||
Long header content is shortened. The complete text remains available in its tooltip.
|
||||
|
||||
### Clearing the Selection with Reset View
|
||||
|
||||
**Reset view** clears the selected station without changing the map position or zoom level.
|
||||
|
||||
It:
|
||||
|
||||
- clears the selected station,
|
||||
- clears the selection in the main user list,
|
||||
- removes the connection line to the remote station,
|
||||
- discards a pending analysis for the previous station, and
|
||||
- removes the right-hand analysis panel.
|
||||
|
||||
The map itself remains at the previously selected position and zoom level. This function is therefore not a geographical reset to the local station.
|
||||
|
||||

|
||||
|
||||
Selecting another individual marker restores the station selection and analysis panel.
|
||||
|
||||
### Triggering a DX Cluster Spot
|
||||
|
||||
**Trigger cluster spot** is visible only while a station is selected. It sends one spot to logging programmes connected to the built-in local DX Cluster server.
|
||||
|
||||
This requires:
|
||||
|
||||
- the local DX Cluster server to be enabled,
|
||||
- at least one connected cluster client, and
|
||||
- a usable QRG for the selected station.
|
||||
|
||||
The spot is not sent to a public Internet cluster.
|
||||
|
||||
### Path Analysis
|
||||
|
||||
The terrain profile is displayed below the map. The right-hand analysis panel includes, among other things:
|
||||
|
||||
- the data source and number of elevation samples,
|
||||
- the analysis frequency,
|
||||
- line-of-sight and horizon information,
|
||||
- the Earth-curvature or refraction model,
|
||||
- radio and terrain horizons,
|
||||
- Fresnel-zone clearance,
|
||||
- detected obstructions,
|
||||
- an estimated link budget,
|
||||
- received power and SSB margin, and
|
||||
- a short assessment of the path.
|
||||
- the link budget,
|
||||
- estimated received power, and
|
||||
- a summarised path assessment.
|
||||
|
||||
Moving the mouse over the terrain profile highlights the corresponding geographical position on the map.
|
||||
The analysis uses the same centrally derived band as the Reachability functions. A band explicitly selected in the **Reachability** dropdown is taken into account.
|
||||
|
||||
The analysis can be hidden using **Hide path analysis** when more space is required for the map. The compact state displays **Path analysis is hidden.** together with the **Show path analysis** button.
|
||||
These values remain technical estimates. Buildings, vegetation, local obstructions, current propagation conditions and unknown station parameters may substantially change the real result.
|
||||
|
||||
### Hiding the Path Analysis
|
||||
|
||||
**Hide path analysis** hides both the terrain profile and the right-hand analysis panel, leaving more space for the map.
|
||||
|
||||

|
||||
|
||||
The selected station and map contents remain available while the analysis panel is hidden. The setting is stored and restored at the next start.
|
||||
The **Path analysis is hidden** message and **Show path analysis** button remain visible, so the function can be restored directly.
|
||||
|
||||
Calculation method and limitations: [Station Map and Path Analysis](en-Features#station-map-and-path-analysis-from-v141).
|
||||
If no station is selected when the analysis is shown again, no empty right-hand panel is displayed. It is recreated only after a specific station has been selected.
|
||||
|
||||
The setting is stored and restored at the next programme start.
|
||||
|
||||
The divider between the map and detail panel can be moved horizontally. Longer values wrap in a narrow detail panel; a vertical scrollbar appears if the available height is insufficient.
|
||||
|
||||
Detailed derivation and limitations: [Station Map and Path Analysis](en-Features#station-map-and-path-analysis-from-v141).
|
||||
|
||||
---
|
||||
|
||||
## Global Message Tabs and Monitor Window
|
||||
|
||||
Three global message tabs are located below the main user list. Unlike the **Further Info** panel, their contents do not depend on the station currently selected.
|
||||
The lower part of the main window contains three global message tabs. Their contents do not depend on the station currently selected in the user list.
|
||||
|
||||
| Tab | Displayed messages |
|
||||
| Tab | Content |
|
||||
|---|---|
|
||||
| **Public messages** | All public chat messages, including CQ calls and beacons |
|
||||
| **DXCluster messages** | DX cluster messages received from the ON4KST server |
|
||||
| **QSO of the other** | Directed messages between chat logins other than the local station |
|
||||
| **Public messages** | Public chat messages, CQ calls and beacons |
|
||||
| **DXCluster messages** | DX cluster messages received through ON4KST |
|
||||
| **QSO of the other** | Directed messages between two other stations |
|
||||
|
||||
The **Public messages** tab is selected by default.
|
||||

|
||||
|
||||

|
||||
In **QSO of the other**, sender and receiver are displayed separately. **Last QRG TX** and **Last QRG RX** contain the frequencies most recently known for the two stations. They do not necessarily represent the frequency discussed in the displayed conversation.
|
||||
|
||||
The **DXCluster messages** table contains the time, reporting and reported stations, locators, QRG, message text and global Worked state where these values are available in the received message.
|
||||
**wkd TX?** and **wkd RX?** show the global Worked state of the two base callsigns. These values are not band-specific.
|
||||
|
||||
The **QSO of the other** table contains:
|
||||
The **DXCluster messages** tab shows the reporting and reported stations, their locators, QRG, message text and the global Worked state of the reported station. Which fields are actually available depends on the message received from the ON4KST server.
|
||||
|
||||
- the complete sender and receiver callsigns,
|
||||
- the latest QRG currently known for each station,
|
||||
- the global Worked state of each station,
|
||||
- the message text, and
|
||||
- the chat category.
|
||||
Message text remains on one line. If a cell is too narrow, its complete content is available in a tooltip. Web addresses in the message text are clickable.
|
||||
|
||||
The displayed QRG is not necessarily the frequency on which the stations intend to make a contact. It is the latest QRG currently associated with the respective chat member. The Worked state is global and not specific to the displayed QRG or band.
|
||||
### Separate Monitor Window
|
||||
|
||||
A directed chat message in this table does not prove that a radio QSO has taken place. The table also contains sked requests, frequency exchanges and other directed messages between third-party chat logins.
|
||||
|
||||
### Separate monitor window
|
||||
|
||||
The DX cluster and QSO-of-the-other tables can also be displayed together in a separate window.
|
||||
KST4Contest additionally opens the **Cluster & QSO of the other** window. It shows DX cluster messages in the upper table and directed messages between other stations in the lower table.
|
||||
|
||||

|
||||
|
||||
The separate window and the tabs use the same underlying messages. Hiding the window does not stop message processing or remove messages from the tabs.
|
||||
The vertical divider position and window size are stored together with the other UI settings. Use **Save Settings** after changing them.
|
||||
|
||||
Use **Windows → Hide cluster / stranger QSOs** to hide the window and **Windows → Show cluster / stranger QSOs** to restore it.
|
||||
The window can be hidden and restored through:
|
||||
|
||||
If a message is too long for its table cell, moving the mouse over the cell displays the complete text in a tooltip. Links beginning with `http://`, `https://` or `www.` can be opened in the system browser.
|
||||
```text
|
||||
Windows → Hide cluster / stranger QSOs
|
||||
Windows → Show cluster / stranger QSOs
|
||||
```
|
||||
|
||||
The main-window tabs and separate monitor window use the same underlying data. Hiding the monitor window therefore neither stops message processing nor removes messages from the tabs.
|
||||
|
||||
Derivation and limitations: [Global Message Views](en-Features#global-message-views).
|
||||
|
||||
---
|
||||
|
||||
## Menu
|
||||
|
||||
### File
|
||||
|
||||
- **Connect to …** starts the connection using the settings already applied in KST4Contest.
|
||||
- **Disconnect** terminates the current ON4KST connection without closing KST4Contest.
|
||||
- **Exit + disconnect** terminates the connection and then closes the programme.
|
||||
|
||||
The Connect and Disconnect entries are enabled or disabled according to the current connection state.
|
||||
|
||||
### Options
|
||||
|
||||
- **Set QRG as name in Chat (main category)** sends `/SETNAME` containing the current `MYQRG` to the primary chat category.
|
||||
- **Show me as away in chat** sends `/AWAY`.
|
||||
- **Show me as active in chat** sends `/BACK`.
|
||||
- **Show options** shows or hides the settings window.
|
||||
|
||||
Functions which communicate with the server are available only after the ON4KST connection has been established completely.
|
||||
|
||||
### Windows
|
||||
|
||||
- **Hide cluster / stranger QSOs** hides the separate monitor window for DX cluster messages and directed messages between other stations.
|
||||
- **Show cluster / stranger QSOs** restores the monitor window.
|
||||
- **hide options** hides the settings window.
|
||||
- **show options** restores the settings window.
|
||||
- **Hide cluster / stranger QSOs** and **Show cluster / stranger QSOs** hide or restore the separate cluster and QSO monitor window.
|
||||
- **hide options** and **show options** hide or restore the settings window.
|
||||
- **Use dark mode design** activates the dark colour scheme.
|
||||
- **Use default mode design** restores the default colour scheme.
|
||||
- **Use default mode design** restores the standard light colour scheme.
|
||||
- **Show / hide station map** opens or closes the separate station-map and path-analysis window.
|
||||
|
||||
---
|
||||
@@ -270,8 +495,7 @@ If the layout has become inconvenient, first move the dividers back to usable po
|
||||
|
||||
## Operating Tips
|
||||
|
||||
- **Keep the settings window open**: Quick access to enable/disable the beacon.
|
||||
- **Right-click in the user list**: Opens the snippet menu and other context actions.
|
||||
- **Mark a station NOT QRV**: Select the station and use the per-band controls in the **Further Info** panel.
|
||||
- **Enter from anywhere**: When text is in the send field, Enter sends directly – even if the focus is elsewhere.
|
||||
- **Stop the beacon**: Switch off the beacon while scanning frequencies to avoid flooding the chat with messages.
|
||||
- **Keep the settings window open**: This provides quick access to the beacon controls.
|
||||
- **Right-click in the user list**: Opens the snippet menu and additional actions, including QRZ.com profiles and NOT-QRV marks.
|
||||
- **Press Enter while working in the chat**: If the send field contains text, Enter sends it directly even when another control has focus.
|
||||
- **Stop the beacon while scanning**: Disable the beacon while moving through frequencies to avoid flooding the chat with unnecessary messages.
|
||||
|
||||
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Derives the jpackage --add-modules list from module-info.java so that the
|
||||
* packaging scripts never drift from the module descriptor again.
|
||||
*
|
||||
* Run as a single file source program, which behaves identically on the Linux,
|
||||
* macOS and Windows runners:
|
||||
*
|
||||
* java packaging/AddModules.java print the module list
|
||||
* java packaging/AddModules.java --verify-pom fail if pom.xml drifted
|
||||
*
|
||||
* Only platform modules are emitted. Third party requires such as jlayer are
|
||||
* skipped because they are supplied as ordinary jars on the class path, and
|
||||
* automatic modules cannot be linked into a runtime image at all. Test only
|
||||
* requires such as org.junit.jupiter.api are skipped for the same reason.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class AddModules {
|
||||
|
||||
private static final Path DESCRIPTOR =
|
||||
Path.of("src", "main", "java", "module-info.java");
|
||||
|
||||
private static final Path POM = Path.of("pom.xml");
|
||||
|
||||
/** Matches "requires [transitive] [static] some.module;" in any order. */
|
||||
private static final Pattern REQUIRES = Pattern.compile(
|
||||
"requires\\s+((?:transitive\\s+|static\\s+)*)([A-Za-z0-9_.]+)\\s*;");
|
||||
|
||||
private static final Pattern ADD_MODULE =
|
||||
Pattern.compile("<addmodule>\\s*([A-Za-z0-9_.]+)\\s*</addmodule>");
|
||||
|
||||
private static final Pattern BLOCK_COMMENT =
|
||||
Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL);
|
||||
|
||||
private static final Pattern LINE_COMMENT = Pattern.compile("//[^\\n]*");
|
||||
|
||||
private AddModules() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
boolean verifyPom = args.length > 0 && "--verify-pom".equals(args[0]);
|
||||
|
||||
Set<String> required = platformModules(read(DESCRIPTOR));
|
||||
if (required.isEmpty()) {
|
||||
fail("No platform modules found in " + DESCRIPTOR);
|
||||
}
|
||||
|
||||
if (!verifyPom) {
|
||||
System.out.println(String.join(",", required));
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> declared = new TreeSet<>();
|
||||
Matcher matcher = ADD_MODULE.matcher(read(POM));
|
||||
while (matcher.find()) {
|
||||
declared.add(matcher.group(1));
|
||||
}
|
||||
|
||||
if (declared.equals(required)) {
|
||||
System.out.println("pom.xml <addmodules> matches module-info.java ("
|
||||
+ required.size() + " modules)");
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> missing = new TreeSet<>(required);
|
||||
missing.removeAll(declared);
|
||||
Set<String> extra = new TreeSet<>(declared);
|
||||
extra.removeAll(required);
|
||||
|
||||
System.err.println("pom.xml <addmodules> drifted from module-info.java.");
|
||||
if (!missing.isEmpty()) {
|
||||
System.err.println(" missing in pom.xml: " + String.join(", ", missing));
|
||||
}
|
||||
if (!extra.isEmpty()) {
|
||||
System.err.println(" not required by module-info.java: "
|
||||
+ String.join(", ", extra));
|
||||
}
|
||||
System.err.println(" expected: " + String.join(",", required));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
/** Returns the platform modules required by the given descriptor, sorted. */
|
||||
static Set<String> platformModules(String source) {
|
||||
String stripped = LINE_COMMENT.matcher(
|
||||
BLOCK_COMMENT.matcher(source).replaceAll(" ")).replaceAll(" ");
|
||||
|
||||
Set<String> modules = new TreeSet<>();
|
||||
Matcher matcher = REQUIRES.matcher(stripped);
|
||||
while (matcher.find()) {
|
||||
// "requires static" is a compile time only dependency and must not
|
||||
// be linked into the shipped runtime image.
|
||||
if (matcher.group(1).contains("static")) {
|
||||
continue;
|
||||
}
|
||||
String module = matcher.group(2);
|
||||
if (isPlatformModule(module)) {
|
||||
modules.add(module);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
private static boolean isPlatformModule(String module) {
|
||||
return module.startsWith("java.")
|
||||
|| module.startsWith("jdk.")
|
||||
|| module.startsWith("javafx.");
|
||||
}
|
||||
|
||||
private static String read(Path path) throws IOException {
|
||||
if (!Files.isRegularFile(path)) {
|
||||
fail("Not found: " + path.toAbsolutePath()
|
||||
+ " (run this from the repository root)");
|
||||
}
|
||||
return Files.readString(path);
|
||||
}
|
||||
|
||||
private static void fail(String message) {
|
||||
System.err.println(message);
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = kst4contest-bin
|
||||
pkgdesc = ON4KST Chat Client for VHF/UHF contest operation (pre-built)
|
||||
pkgver = 1.41.1
|
||||
pkgver = 1.42.0
|
||||
pkgrel = 1
|
||||
url = https://github.com/praktimarc/kst4contest
|
||||
arch = x86_64
|
||||
@@ -10,7 +10,7 @@ pkgbase = kst4contest-bin
|
||||
provides = kst4contest
|
||||
conflicts = kst4contest
|
||||
conflicts = kst4contest-git
|
||||
source = KST4Contest-v1.41.1-archlinux-x86_64.pkg.tar.zst::https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-archlinux-x86_64.pkg.tar.zst
|
||||
sha256sums = 8e9a53ff832920c9ef2733635b90c5a4ffcd57a2958aaa251e92bd031142c614
|
||||
source = KST4Contest-v1.42.0-archlinux-x86_64.pkg.tar.zst::https://github.com/praktimarc/kst4contest/releases/download/v1.42.0/KST4Contest-v1.42.0-archlinux-x86_64.pkg.tar.zst
|
||||
sha256sums = 3d8ac19c9f9d3ab0bdaf0ea621de0aa18c64f442c8776d28bfad607522aaf02b
|
||||
|
||||
pkgname = kst4contest-bin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Philipp Wagner <philipp@wagnersnetz.de>
|
||||
pkgname=kst4contest-bin
|
||||
pkgver=1.41.1
|
||||
pkgver=1.42.0
|
||||
pkgrel=1
|
||||
pkgdesc="ON4KST Chat Client for VHF/UHF contest operation (pre-built)"
|
||||
arch=('x86_64')
|
||||
@@ -10,7 +10,7 @@ depends=('gst-plugins-base' 'gst-plugins-good')
|
||||
provides=('kst4contest')
|
||||
conflicts=('kst4contest' 'kst4contest-git')
|
||||
source=("KST4Contest-v${pkgver}-archlinux-${CARCH}.pkg.tar.zst::https://github.com/praktimarc/kst4contest/releases/download/v${pkgver}/KST4Contest-v${pkgver}-archlinux-${CARCH}.pkg.tar.zst")
|
||||
sha256sums=('8e9a53ff832920c9ef2733635b90c5a4ffcd57a2958aaa251e92bd031142c614')
|
||||
sha256sums=('3d8ac19c9f9d3ab0bdaf0ea621de0aa18c64f442c8776d28bfad607522aaf02b')
|
||||
|
||||
package() {
|
||||
cp -a "${srcdir}/usr" "${pkgdir}/"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = kst4contest-git
|
||||
pkgdesc = ON4KST Chat Client for VHF/UHF contest operation (git)
|
||||
pkgver = 1.42.0.r145.gd885924
|
||||
pkgver = 1.42.0.r256.g8aadbb9
|
||||
pkgrel = 1
|
||||
url = https://github.com/praktimarc/kst4contest
|
||||
arch = x86_64
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Philipp Wagner <philipp@wagnersnetz.de>
|
||||
pkgname=kst4contest-git
|
||||
pkgver=1.42.0.r145.gd885924
|
||||
pkgver=1.42.0.r256.g8aadbb9
|
||||
pkgrel=1
|
||||
pkgdesc="ON4KST Chat Client for VHF/UHF contest operation (git)"
|
||||
arch=('x86_64')
|
||||
@@ -33,14 +33,16 @@ build() {
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--icon packaging/icons/kst4contest.png \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = kst4contest
|
||||
pkgdesc = ON4KST Chat Client for VHF/UHF contest operation
|
||||
pkgver = 1.41.1
|
||||
pkgver = 1.42.0
|
||||
pkgrel = 1
|
||||
url = https://github.com/praktimarc/kst4contest
|
||||
arch = x86_64
|
||||
@@ -12,7 +12,7 @@ pkgbase = kst4contest
|
||||
provides = kst4contest
|
||||
conflicts = kst4contest-bin
|
||||
conflicts = kst4contest-git
|
||||
source = kst4contest-1.41.1.tar.gz::https://github.com/praktimarc/kst4contest/archive/refs/tags/v1.41.1.tar.gz
|
||||
sha256sums = e96207a2d3fee19d35e34717f5312beb28bb087c040164e352337e749ce53b8d
|
||||
source = kst4contest-1.42.0.tar.gz::https://github.com/praktimarc/kst4contest/archive/refs/tags/v1.42.0.tar.gz
|
||||
sha256sums = bd396387b8de41aac706458d5ebf64140ab3e83b8a7c710b66aa802bd48e804e
|
||||
|
||||
pkgname = kst4contest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Philipp Wagner <philipp@wagnersnetz.de>
|
||||
pkgname=kst4contest
|
||||
pkgver=1.41.1
|
||||
pkgver=1.42.0
|
||||
pkgrel=1
|
||||
pkgdesc="ON4KST Chat Client for VHF/UHF contest operation"
|
||||
arch=('x86_64')
|
||||
@@ -11,7 +11,7 @@ makedepends=('java-environment=21' 'maven')
|
||||
provides=('kst4contest')
|
||||
conflicts=('kst4contest-bin' 'kst4contest-git')
|
||||
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/praktimarc/kst4contest/archive/refs/tags/v${pkgver}.tar.gz")
|
||||
sha256sums=('e96207a2d3fee19d35e34717f5312beb28bb087c040164e352337e749ce53b8d')
|
||||
sha256sums=('bd396387b8de41aac706458d5ebf64140ab3e83b8a7c710b66aa802bd48e804e')
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/kst4contest-${pkgver}"
|
||||
@@ -26,14 +26,30 @@ build() {
|
||||
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
|
||||
|
||||
mkdir -p dist
|
||||
# This PKGBUILD builds from a released source tarball, which may predate
|
||||
# packaging/AddModules.java. Older tarballs carry the same list in pom.xml,
|
||||
# which the build keeps in sync with module-info.java from v1.42.0 onwards.
|
||||
if [ -f packaging/AddModules.java ]; then
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
else
|
||||
ADD_MODULES="$(sed -n 's:.*<addmodule>\(.*\)</addmodule>.*:\1:p' pom.xml | paste -sd,)"
|
||||
fi
|
||||
# Same story for the packaging icon: without --icon jpackage silently ships
|
||||
# its own Duke placeholder, but tarballs older than v1.42.0 have no icon to
|
||||
# point at, so only pass the flag when the file is actually there.
|
||||
ICON_ARGS=()
|
||||
if [ -f packaging/icons/kst4contest.png ]; then
|
||||
ICON_ARGS=(--icon packaging/icons/kst4contest.png)
|
||||
fi
|
||||
jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
"${ICON_ARGS[@]}" \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--dest dist
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180" width="180" height="180"><rect x="0" y="0" width="180" height="180" rx="40" fill="#5fd63a"></rect><g transform="translate(40,40) scale(1)">
|
||||
<path d="M20 34 h60 a12 12 0 0 1 12 12 v20 a12 12 0 0 1 -12 12 h-30 l-16 12 v-12 h-14 a12 12 0 0 1 -12 -12 v-20 a12 12 0 0 1 12 -12 z" fill="#0a110a"></path>
|
||||
<text x="50" y="65" text-anchor="middle" font-family="'Space Grotesk','Helvetica Neue',Arial,sans-serif" font-weight="700" font-size="24" letter-spacing="0.5" fill="#5fd63a">KST</text>
|
||||
<line x1="50" y1="34" x2="50" y2="17" stroke="#0a110a" stroke-width="6" stroke-linecap="round"></line>
|
||||
<circle cx="50" cy="13" r="4.5" fill="#0a110a"></circle>
|
||||
<path d="M58 6 a9 9 0 0 1 0 14" fill="none" stroke="#0a110a" stroke-width="4" stroke-linecap="round"></path>
|
||||
<path d="M64 1 a15 15 0 0 1 0 24" fill="none" stroke="#0a110a" stroke-width="4" stroke-linecap="round"></path>
|
||||
<path d="M42 6 a9 9 0 0 0 0 14" fill="none" stroke="#0a110a" stroke-width="4" stroke-linecap="round"></path>
|
||||
<path d="M36 1 a15 15 0 0 0 0 24" fill="none" stroke="#0a110a" stroke-width="4" stroke-linecap="round"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Local signed (and optionally notarized) macOS build.
|
||||
#
|
||||
# jpackage cannot sign the app itself: it ad-hoc signs the embedded runtime and
|
||||
# then re-runs codesign on the same files without --force, which codesign
|
||||
# rejects with "is already signed". So this builds an unsigned app-image, signs
|
||||
# it from the inside out ourselves, and only then wraps it into a DMG.
|
||||
#
|
||||
# Required:
|
||||
# SIGNING_IDENTITY The name part of the Developer ID Application certificate,
|
||||
# without the "Developer ID Application: " prefix. Example:
|
||||
# SIGNING_IDENTITY="Philipp Wagner (ABCDE12345)"
|
||||
# List available ones with:
|
||||
# security find-identity -v -p codesigning
|
||||
#
|
||||
# Optional:
|
||||
# Notarization, either as three separate values (what CI uses)...
|
||||
# NOTARY_KEY Path to the App Store Connect .p8 private key
|
||||
# NOTARY_KEY_ID The key's ID, also part of the .p8 filename
|
||||
# NOTARY_ISSUER The issuer UUID, shown above the key list in the portal
|
||||
# ...or as a keychain profile previously created with
|
||||
# NOTARY_PROFILE xcrun notarytool store-credentials <name>
|
||||
#
|
||||
# With neither, the build is signed but not notarized -- enough to test
|
||||
# locally, not enough to distribute.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
REPO_ROOT="$PWD"
|
||||
|
||||
BUNDLE_ID="de.x08.KST4Contest"
|
||||
ENTITLEMENTS="packaging/macos/kst4contest.entitlements"
|
||||
|
||||
if [ -z "${SIGNING_IDENTITY:-}" ]; then
|
||||
echo "SIGNING_IDENTITY is not set. Available signing identities:" >&2
|
||||
security find-identity -v -p codesigning >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
FULL_IDENTITY="Developer ID Application: $SIGNING_IDENTITY"
|
||||
|
||||
# notarytool takes either an API key triple or a stored keychain profile. The
|
||||
# triple needs no keychain at all, which is why CI uses it.
|
||||
NOTARY_ARGS=()
|
||||
if [ -n "${NOTARY_KEY:-}" ] && [ -n "${NOTARY_KEY_ID:-}" ] && [ -n "${NOTARY_ISSUER:-}" ]; then
|
||||
NOTARY_ARGS=(--key "$NOTARY_KEY" --key-id "$NOTARY_KEY_ID" --issuer "$NOTARY_ISSUER")
|
||||
elif [ -n "${NOTARY_PROFILE:-}" ]; then
|
||||
NOTARY_ARGS=(--keychain-profile "$NOTARY_PROFILE")
|
||||
fi
|
||||
|
||||
echo "==> Building JAR and collecting runtime dependencies"
|
||||
chmod +x mvnw
|
||||
./mvnw -B -DskipTests package \
|
||||
dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
|
||||
JAR="$(ls -t target/praktiKST-*.jar | head -n 1)"
|
||||
cp "$JAR" target/dist-libs/app.jar
|
||||
|
||||
# jpackage only accepts a numeric major[.minor[.patch]] as the macOS bundle
|
||||
# version, so a Maven qualifier like "-nightly" has to be trimmed off.
|
||||
POM_VERSION="${JAR##*/praktiKST-}"
|
||||
POM_VERSION="${POM_VERSION%.jar}"
|
||||
APP_VERSION="$(printf '%s' "$POM_VERSION" | sed -e 's/[^0-9.].*$//' -e 's/\.*$//')"
|
||||
[ -n "$APP_VERSION" ] || { echo "Could not derive app version from $JAR" >&2; exit 1; }
|
||||
echo "==> Version: $POM_VERSION -> bundle version $APP_VERSION"
|
||||
|
||||
echo "==> Step 1/4: jpackage app-image (unsigned)"
|
||||
rm -rf dist
|
||||
mkdir -p dist
|
||||
ADD_MODULES="$(java packaging/AddModules.java)"
|
||||
|
||||
MACOSX_DEPLOYMENT_TARGET="13.0" jpackage \
|
||||
--type app-image \
|
||||
--name KST4Contest \
|
||||
--app-version "$APP_VERSION" \
|
||||
--icon packaging/icons/kst4contest.icns \
|
||||
--input target/dist-libs \
|
||||
--main-jar app.jar \
|
||||
--main-class kst4contest.view.Kst4ContestApplication \
|
||||
--module-path target/dist-libs \
|
||||
--add-modules "$ADD_MODULES" \
|
||||
--mac-package-identifier "$BUNDLE_ID" \
|
||||
--mac-package-name KST4Contest \
|
||||
--dest dist/appimage
|
||||
|
||||
APP="dist/appimage/KST4Contest.app"
|
||||
[ -d "$APP" ] || { echo "jpackage produced no app image" >&2; exit 1; }
|
||||
|
||||
echo "==> Step 2/4: signing bundle contents (this takes a few minutes)"
|
||||
|
||||
# Apple's notary service unpacks JARs and checks the native libraries inside
|
||||
# them. sqlite-jdbc ships libsqlitejdbc.dylib for both architectures that way,
|
||||
# and an unsigned binary in there fails the whole submission. So sign those
|
||||
# first: the app bundle's seal covers Contents/app, and rewriting a JAR
|
||||
# afterwards would invalidate it.
|
||||
echo " scanning jars for native libraries"
|
||||
find "$APP/Contents/app" -name '*.jar' -type f | while read -r JARPATH; do
|
||||
# Only unpack jars that can plausibly hold a native library. Note the
|
||||
# plain grep: "grep -q" exits at the first match, which hands unzip a
|
||||
# SIGPIPE, and under "set -o pipefail" that failure becomes the pipeline's
|
||||
# status -- inverting this very test.
|
||||
if ! unzip -l "$JARPATH" | grep -E '\.(dylib|jnilib|so)$' >/dev/null; then
|
||||
continue
|
||||
fi
|
||||
|
||||
JARABS="$(cd "$(dirname "$JARPATH")" && pwd)/$(basename "$JARPATH")"
|
||||
JARTMP="$(mktemp -d)"
|
||||
unzip -q "$JARABS" -d "$JARTMP"
|
||||
|
||||
NATIVES="$(mktemp)"
|
||||
( cd "$JARTMP" && find . -type f \( -name '*.dylib' -o -name '*.jnilib' -o -name '*.so' \) \
|
||||
| while read -r n; do
|
||||
if [ "$(file --mime-type -b "$n")" = "application/x-mach-binary" ]; then
|
||||
printf '%s\n' "${n#./}"
|
||||
fi
|
||||
done ) > "$NATIVES"
|
||||
|
||||
if [ -s "$NATIVES" ]; then
|
||||
echo " $(basename "$JARPATH"): $(wc -l < "$NATIVES" | tr -d ' ') native lib(s)"
|
||||
( cd "$JARTMP" && xargs -I {} codesign --force --timestamp --options runtime \
|
||||
--sign "$FULL_IDENTITY" {} < "$NATIVES" )
|
||||
# Update in place rather than repacking, so the rest of the jar --
|
||||
# manifest, module descriptor, entry order -- stays byte for byte.
|
||||
( cd "$JARTMP" && xargs jar --update --file "$JARABS" < "$NATIVES" )
|
||||
fi
|
||||
|
||||
rm -rf "$JARTMP" "$NATIVES"
|
||||
done
|
||||
|
||||
# Every Mach-O file has to carry its own signature before the enclosing bundle
|
||||
# can be sealed, so collect them first. jpackage leaves them ad-hoc signed,
|
||||
# hence --force on every call.
|
||||
# file(1) pads its output into columns when given several arguments at once,
|
||||
# so ask it one file at a time with -b and get an unambiguous answer.
|
||||
MACHO_LIST="$(mktemp)"
|
||||
find "$APP" -type f -print0 | while IFS= read -r -d '' f; do
|
||||
case "$(file --mime-type -b "$f")" in
|
||||
application/x-mach-binary) printf '%s\n' "$f" ;;
|
||||
esac
|
||||
done > "$MACHO_LIST"
|
||||
|
||||
COUNT="$(wc -l < "$MACHO_LIST" | tr -d ' ')"
|
||||
echo " $COUNT Mach-O files to sign"
|
||||
|
||||
# Serially, deliberately. Running codesign concurrently over several files of
|
||||
# the same bundle fails intermittently -- a CI run died with "replacing existing
|
||||
# signature" immediately followed by "No such file or directory" for that same
|
||||
# path, while the identical script passed locally. Each call contacts Apple's
|
||||
# timestamp server, so this costs about a minute for a runtime this size.
|
||||
xargs -I {} codesign --force --timestamp --options runtime \
|
||||
--sign "$FULL_IDENTITY" {} < "$MACHO_LIST"
|
||||
rm -f "$MACHO_LIST"
|
||||
|
||||
# The embedded JDK is a bundle in its own right and must be sealed before the
|
||||
# app that contains it.
|
||||
echo " sealing embedded runtime"
|
||||
codesign --force --timestamp --options runtime \
|
||||
--sign "$FULL_IDENTITY" "$APP/Contents/runtime"
|
||||
|
||||
# Entitlements go on the outermost bundle: the hardened runtime derives the
|
||||
# process's entitlements from the main executable's signature.
|
||||
echo " sealing app bundle"
|
||||
codesign --force --timestamp --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$FULL_IDENTITY" "$APP"
|
||||
|
||||
# Apple rejects the whole submission over a single unsigned native library, and
|
||||
# a round trip to the notary service costs minutes. Check its two criteria --
|
||||
# a Developer ID authority and a secure timestamp -- locally first.
|
||||
echo " preflight: verifying native libraries inside jars"
|
||||
PREFLIGHT_ERRORS="$(mktemp)"
|
||||
find "$APP/Contents/app" -name '*.jar' -type f | while read -r JARPATH; do
|
||||
if ! unzip -l "$JARPATH" | grep -E '\.(dylib|jnilib|so)$' >/dev/null; then
|
||||
continue
|
||||
fi
|
||||
CHECKTMP="$(mktemp -d)"
|
||||
unzip -q "$JARPATH" -d "$CHECKTMP"
|
||||
find "$CHECKTMP" -type f \( -name '*.dylib' -o -name '*.jnilib' -o -name '*.so' \) \
|
||||
| while read -r NATIVE; do
|
||||
[ "$(file --mime-type -b "$NATIVE")" = "application/x-mach-binary" ] || continue
|
||||
INFO="$(codesign -dv --verbose=2 "$NATIVE" 2>&1 || true)"
|
||||
LABEL="$(basename "$JARPATH")/${NATIVE#"$CHECKTMP"/}"
|
||||
printf '%s' "$INFO" | grep -q "Authority=Developer ID Application" \
|
||||
|| echo "$LABEL: not signed with a Developer ID certificate" >> "$PREFLIGHT_ERRORS"
|
||||
printf '%s' "$INFO" | grep -q "Timestamp=" \
|
||||
|| echo "$LABEL: signature has no secure timestamp" >> "$PREFLIGHT_ERRORS"
|
||||
done
|
||||
rm -rf "$CHECKTMP"
|
||||
done
|
||||
if [ -s "$PREFLIGHT_ERRORS" ]; then
|
||||
echo "ERROR: these would fail notarization:" >&2
|
||||
sed 's/^/ /' "$PREFLIGHT_ERRORS" >&2
|
||||
rm -f "$PREFLIGHT_ERRORS"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$PREFLIGHT_ERRORS"
|
||||
echo " preflight ok"
|
||||
|
||||
echo "==> Step 3/4: building the dmg"
|
||||
|
||||
# Not with jpackage: "jpackage --type dmg --app-image" re-signs the app it is
|
||||
# handed, replacing our Developer ID signature with an ad-hoc one and dropping
|
||||
# the hardened runtime flag. hdiutil copies the bundle verbatim instead.
|
||||
DMG="dist/KST4Contest-${APP_VERSION}.dmg"
|
||||
STAGE="$(mktemp -d)"
|
||||
# ditto rather than cp -R: it preserves the extended attributes the code
|
||||
# signature depends on.
|
||||
ditto "$APP" "$STAGE/KST4Contest.app"
|
||||
ln -s /Applications "$STAGE/Applications"
|
||||
|
||||
hdiutil create -volname "KST4Contest" -srcfolder "$STAGE" \
|
||||
-ov -format UDZO -quiet "$DMG"
|
||||
rm -rf "$STAGE"
|
||||
[ -f "$DMG" ] || { echo "hdiutil produced no DMG" >&2; exit 1; }
|
||||
|
||||
# Signing the DMG itself is not what Gatekeeper judges -- that is the .app
|
||||
# inside -- but Apple expects the container to be signed too.
|
||||
codesign --force --timestamp --sign "$FULL_IDENTITY" "$DMG"
|
||||
echo "==> Built $DMG"
|
||||
|
||||
echo "==> Step 4/4: verification"
|
||||
|
||||
if [ ${#NOTARY_ARGS[@]} -gt 0 ]; then
|
||||
echo " submitting for notarization (waits for Apple's verdict)"
|
||||
# Without a timeout a stalled submission would hang a CI job forever.
|
||||
xcrun notarytool submit "$DMG" "${NOTARY_ARGS[@]}" --wait --timeout 30m
|
||||
echo " stapling ticket"
|
||||
xcrun stapler staple "$DMG"
|
||||
else
|
||||
echo " no notarization credentials set, skipping notarization"
|
||||
fi
|
||||
|
||||
# Everything below inspects the app as it actually ships, mounted from the DMG,
|
||||
# rather than the staging copy on disk.
|
||||
MOUNT_POINT="$(mktemp -d)"
|
||||
hdiutil attach "$DMG" -nobrowse -quiet -mountpoint "$MOUNT_POINT"
|
||||
trap 'hdiutil detach "$MOUNT_POINT" -quiet 2>/dev/null || hdiutil detach "$MOUNT_POINT" -force -quiet 2>/dev/null || true' EXIT
|
||||
SHIPPED_APP="$MOUNT_POINT/KST4Contest.app"
|
||||
|
||||
echo "--- codesign --verify on the app inside the DMG ---"
|
||||
codesign --verify --deep --strict --verbose=2 "$SHIPPED_APP"
|
||||
|
||||
echo "--- app identity ---"
|
||||
codesign -dv --verbose=2 "$SHIPPED_APP" 2>&1 | grep -iE "identifier|authority|teamidentifier|flags"
|
||||
|
||||
# An ad-hoc signature here means something along the way re-signed the bundle.
|
||||
if codesign -dv "$SHIPPED_APP" 2>&1 | grep -q "adhoc"; then
|
||||
echo "ERROR: the app inside the DMG is ad-hoc signed, not Developer ID signed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- entitlements as signed ---"
|
||||
codesign -d --entitlements - --xml "$SHIPPED_APP" 2>/dev/null | plutil -convert xml1 -o - - | grep -E "key|true|false"
|
||||
|
||||
echo "--- dmg identity ---"
|
||||
codesign -dv --verbose=2 "$DMG" 2>&1 | grep -iE "authority|teamidentifier" | head -2
|
||||
|
||||
echo "--- spctl assessment ---"
|
||||
# Without notarization this reports "rejected"; that is expected.
|
||||
spctl --assess --type execute --verbose=4 "$SHIPPED_APP" || true
|
||||
|
||||
if [ ${#NOTARY_ARGS[@]} -gt 0 ]; then
|
||||
echo "--- stapler validate ---"
|
||||
xcrun stapler validate "$DMG"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Done: $REPO_ROOT/$DMG"
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Import the Developer ID certificate into a throwaway keychain on a CI runner.
|
||||
#
|
||||
# A runner cannot answer the keychain's authorization dialog, so the login
|
||||
# keychain is unusable there. This creates a dedicated keychain instead, whose
|
||||
# password is generated here and needed nowhere else -- it is discarded with the
|
||||
# keychain at the end of the job.
|
||||
#
|
||||
# Reads from the environment:
|
||||
# MACOS_CERT_P12 base64 of the exported .p12
|
||||
# MACOS_CERT_PASSWORD the password that .p12 was exported with
|
||||
#
|
||||
# Exports to $GITHUB_ENV:
|
||||
# SIGNING_IDENTITY for packaging/macos/build-signed-dmg.sh
|
||||
# SIGNING_KEYCHAIN so the cleanup step knows what to delete
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
: "${MACOS_CERT_P12:?MACOS_CERT_P12 is not set}"
|
||||
: "${MACOS_CERT_PASSWORD:?MACOS_CERT_PASSWORD is not set}"
|
||||
: "${RUNNER_TEMP:?RUNNER_TEMP is not set}"
|
||||
: "${GITHUB_ENV:?GITHUB_ENV is not set}"
|
||||
|
||||
KEYCHAIN="$RUNNER_TEMP/kst4contest-signing.keychain-db"
|
||||
KEYCHAIN_PASSWORD="$(uuidgen)"
|
||||
CERT="$RUNNER_TEMP/cert.p12"
|
||||
|
||||
printf '%s' "$MACOS_CERT_P12" | base64 --decode > "$CERT"
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
||||
# Keychains re-lock after five minutes by default, which would strand a build
|
||||
# halfway through signing.
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
||||
|
||||
security import "$CERT" -k "$KEYCHAIN" -P "$MACOS_CERT_PASSWORD" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security
|
||||
rm -f "$CERT"
|
||||
|
||||
# Lets codesign reach the private key without the UI prompt a runner has no way
|
||||
# of answering.
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: \
|
||||
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" >/dev/null
|
||||
|
||||
# codesign searches the keychain list, so the new keychain has to be on it --
|
||||
# added to whatever the runner already had, not in place of it.
|
||||
EXISTING_KEYCHAINS="$(security list-keychains -d user | sed -e 's/^[[:space:]]*"//' -e 's/"$//')"
|
||||
# shellcheck disable=SC2086
|
||||
security list-keychains -d user -s "$KEYCHAIN" $EXISTING_KEYCHAINS
|
||||
|
||||
IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN" \
|
||||
| sed -n 's/.*"Developer ID Application: \(.*\)".*/\1/p' | head -n 1)"
|
||||
|
||||
if [ -z "$IDENTITY" ]; then
|
||||
echo "No 'Developer ID Application' identity found in the imported certificate." >&2
|
||||
echo "What the keychain does contain:" >&2
|
||||
security find-identity -v -p codesigning "$KEYCHAIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Imported identity: Developer ID Application: $IDENTITY"
|
||||
echo "SIGNING_IDENTITY=$IDENTITY" >> "$GITHUB_ENV"
|
||||
echo "SIGNING_KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV"
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- The JVM compiles bytecode to machine code at runtime and executes it
|
||||
from memory it allocated itself. Under the hardened runtime all three
|
||||
of these are required or the app is killed on launch. -->
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<!-- jpackage bundles JavaFX native libraries that are signed with our own
|
||||
identity rather than Apple's, and the JVM dlopen()s them at runtime. -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
|
||||
<!-- KST4Contest talks to the ON4KST chat servers. -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -56,6 +56,7 @@
|
||||
<javafx.maven.plugin>0.0.8</javafx.maven.plugin>
|
||||
<spotbugs.maven.plugin>4.9.8.2</spotbugs.maven.plugin>
|
||||
<spotbugs.version>4.9.8</spotbugs.version>
|
||||
<exec.maven.plugin>3.1.0</exec.maven.plugin>
|
||||
|
||||
<!-- other properties -->
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -208,6 +209,36 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!--
|
||||
Fails the build whenever the jpackage module list below drifts
|
||||
from the requires clauses in src/main/java/module-info.java.
|
||||
This is bound to validate rather than to a workflow trigger so
|
||||
it also fires on direct pushes to main, on local builds and in
|
||||
the AUR PKGBUILDs, which never run the pull request check.
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>${exec.maven.plugin}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>verify-packaging-module-list</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>${java.home}/bin/java</executable>
|
||||
<workingDirectory>${project.basedir}</workingDirectory>
|
||||
<arguments>
|
||||
<argument>packaging/AddModules.java</argument>
|
||||
<argument>--verify-pom</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
@@ -436,15 +467,26 @@
|
||||
</dependencyset>
|
||||
</dependencysets>
|
||||
</modulepath>
|
||||
<!--
|
||||
Keep in sync with the requires clauses in
|
||||
src/main/java/module-info.java. The packaging
|
||||
scripts derive this list automatically via
|
||||
packaging/AddModules.java, and the PR check
|
||||
runs that helper in its pom verification mode
|
||||
so this block cannot drift unnoticed.
|
||||
-->
|
||||
<addmodules>
|
||||
<addmodule>javafx.controls</addmodule>
|
||||
<addmodule>javafx.graphics</addmodule>
|
||||
<addmodule>javafx.fxml</addmodule>
|
||||
<addmodule>javafx.web</addmodule>
|
||||
<addmodule>javafx.media</addmodule>
|
||||
<addmodule>java.sql</addmodule>
|
||||
<addmodule>java.desktop</addmodule>
|
||||
<addmodule>java.net.http</addmodule>
|
||||
<addmodule>java.sql</addmodule>
|
||||
<addmodule>javafx.controls</addmodule>
|
||||
<addmodule>javafx.fxml</addmodule>
|
||||
<addmodule>javafx.media</addmodule>
|
||||
<addmodule>javafx.web</addmodule>
|
||||
<addmodule>jdk.crypto.ec</addmodule>
|
||||
<addmodule>jdk.jsobject</addmodule>
|
||||
<addmodule>jdk.net</addmodule>
|
||||
<addmodule>jdk.xml.dom</addmodule>
|
||||
</addmodules>
|
||||
<mainclass>${main.class}</mainclass>
|
||||
<input>${project.build.directory}/modules</input>
|
||||
|
||||
@@ -2,6 +2,7 @@ package kst4contest.controller;
|
||||
|
||||
import java.util.TimerTask;
|
||||
|
||||
import kst4contest.model.ChatCategory;
|
||||
import kst4contest.model.ChatMessage;
|
||||
import kst4contest.model.ThreadStateMessage;
|
||||
|
||||
@@ -12,6 +13,10 @@ import kst4contest.model.ThreadStateMessage;
|
||||
* interval. Their enable flags and message templates remain independent. Every
|
||||
* run reads the current preferences, resolves global message variables and
|
||||
* sends only the categories which are currently enabled.</p>
|
||||
*
|
||||
* <p>Beacon messages use the regular outbound chat-message pipeline. They are
|
||||
* not assembled as raw ON4KST frames, because that would bypass the common
|
||||
* category, delimiter and message-text validation.</p>
|
||||
*/
|
||||
public class BeaconTask extends TimerTask {
|
||||
|
||||
@@ -39,35 +44,40 @@ public class BeaconTask extends TimerTask {
|
||||
Thread.currentThread().setName("BeaconTask");
|
||||
reportStatus(THREAD_NICKNAME, true, "initialized", false);
|
||||
|
||||
MessageVariableResolver variableResolver =
|
||||
new MessageVariableResolver(chatController.getChatPreferences());
|
||||
|
||||
sendMainCategoryBeacon(variableResolver);
|
||||
sendSecondCategoryBeacon(variableResolver);
|
||||
sendMainCategoryBeacon();
|
||||
sendSecondCategoryBeacon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the main-category beacon if it is currently enabled.
|
||||
*/
|
||||
private void sendMainCategoryBeacon(MessageVariableResolver variableResolver) {
|
||||
if (!chatController.getChatPreferences().isBcn_beaconsEnabledMainCat()) {
|
||||
reportStatus(THREAD_NICKNAME + " 1", false, "off", false);
|
||||
private void sendMainCategoryBeacon() {
|
||||
if (!chatController.getChatPreferences()
|
||||
.isBcn_beaconsEnabledMainCat()) {
|
||||
reportStatus(
|
||||
THREAD_NICKNAME + " 1",
|
||||
false,
|
||||
"off",
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
String resolvedText = variableResolver.resolveGlobalVariables(
|
||||
chatController.getChatPreferences().getBcn_beaconTextMainCat()
|
||||
);
|
||||
ChatMessage beaconMessage = buildBeaconMessage(
|
||||
chatController.getChatPreferences()
|
||||
.getLoginChatCategoryMain()
|
||||
.getCategoryNumber(),
|
||||
resolvedText,
|
||||
.getLoginChatCategoryMain(),
|
||||
chatController.getChatPreferences()
|
||||
.getBcn_beaconTextMainCat(),
|
||||
"main category"
|
||||
);
|
||||
|
||||
if (beaconMessage == null) {
|
||||
reportStatus(THREAD_NICKNAME + " 1", false, "invalid text", true);
|
||||
reportStatus(
|
||||
THREAD_NICKNAME + " 1",
|
||||
false,
|
||||
"invalid text",
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,37 +86,50 @@ public class BeaconTask extends TimerTask {
|
||||
+ " [BeaconTask, Info]: Sending main-category CQ: "
|
||||
+ beaconMessage.getMessageText()
|
||||
);
|
||||
|
||||
chatController.getMessageTXBus().add(beaconMessage);
|
||||
reportStatus(THREAD_NICKNAME + " 1", true, "on", false);
|
||||
|
||||
reportStatus(
|
||||
THREAD_NICKNAME + " 1",
|
||||
true,
|
||||
"on",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the second-category beacon if the second login and its beacon are
|
||||
* currently enabled.
|
||||
*/
|
||||
private void sendSecondCategoryBeacon(
|
||||
MessageVariableResolver variableResolver
|
||||
) {
|
||||
if (!chatController.getChatPreferences().isLoginToSecondChatEnabled()
|
||||
private void sendSecondCategoryBeacon() {
|
||||
if (!chatController.getChatPreferences()
|
||||
.isLoginToSecondChatEnabled()
|
||||
|| !chatController.getChatPreferences()
|
||||
.isBcn_beaconsEnabledSecondCat()) {
|
||||
reportStatus(THREAD_NICKNAME + " 2", false, "off", false);
|
||||
reportStatus(
|
||||
THREAD_NICKNAME + " 2",
|
||||
false,
|
||||
"off",
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
String resolvedText = variableResolver.resolveGlobalVariables(
|
||||
chatController.getChatPreferences().getBcn_beaconTextSecondCat()
|
||||
);
|
||||
ChatMessage beaconMessage = buildBeaconMessage(
|
||||
chatController.getChatPreferences()
|
||||
.getLoginChatCategorySecond()
|
||||
.getCategoryNumber(),
|
||||
resolvedText,
|
||||
.getLoginChatCategorySecond(),
|
||||
chatController.getChatPreferences()
|
||||
.getBcn_beaconTextSecondCat(),
|
||||
"second category"
|
||||
);
|
||||
|
||||
if (beaconMessage == null) {
|
||||
reportStatus(THREAD_NICKNAME + " 2", false, "invalid text", true);
|
||||
reportStatus(
|
||||
THREAD_NICKNAME + " 2",
|
||||
false,
|
||||
"invalid text",
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,47 +138,65 @@ public class BeaconTask extends TimerTask {
|
||||
+ " [BeaconTask, Info]: Sending second-category CQ: "
|
||||
+ beaconMessage.getMessageText()
|
||||
);
|
||||
|
||||
chatController.getMessageTXBus().add(beaconMessage);
|
||||
reportStatus(THREAD_NICKNAME + " 2", true, "on", false);
|
||||
|
||||
reportStatus(
|
||||
THREAD_NICKNAME + " 2",
|
||||
true,
|
||||
"on",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the server-directed message after validating the resolved payload.
|
||||
* Resolves and validates one beacon before placing it in the regular outbound
|
||||
* message queue.
|
||||
*
|
||||
* <p>The resolved text is checked rather than only the configured template
|
||||
* because inserted values can increase the final message length.</p>
|
||||
* <p>The returned message contains only the public-chat payload and its chat
|
||||
* category. {@link WriteThread} creates the final ON4KST frame through
|
||||
* {@link On4KstProtocol#chatMessage(int, String)}. This prevents a configurable
|
||||
* beacon text from bypassing the common protocol validation.</p>
|
||||
*
|
||||
* @param categoryNumber ON4KST category number
|
||||
* @param resolvedText fully resolved beacon payload
|
||||
* @param category target ON4KST chat category
|
||||
* @param configuredText configured beacon template
|
||||
* @param categoryDescription text used in diagnostic output
|
||||
* @return prepared message, or {@code null} if the payload is invalid
|
||||
* @return prepared message, or {@code null} if the category or text is invalid
|
||||
*/
|
||||
private ChatMessage buildBeaconMessage(
|
||||
int categoryNumber,
|
||||
String resolvedText,
|
||||
ChatCategory category,
|
||||
String configuredText,
|
||||
String categoryDescription
|
||||
) {
|
||||
if (resolvedText == null
|
||||
|| resolvedText.length() > ChatController.MAX_BEACON_TEXT_LENGTH) {
|
||||
int actualLength = resolvedText == null ? 0 : resolvedText.length();
|
||||
try {
|
||||
if (category == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"No chat category is configured."
|
||||
);
|
||||
}
|
||||
|
||||
On4KstProtocol.category(category.getCategoryNumber());
|
||||
|
||||
String resolvedText =
|
||||
chatController.resolveAndValidateBeaconText(
|
||||
configuredText
|
||||
);
|
||||
|
||||
ChatMessage beaconMessage = new ChatMessage();
|
||||
beaconMessage.setMessageText(resolvedText);
|
||||
beaconMessage.setChatCategory(category);
|
||||
beaconMessage.setMessageDirectedToServer(false);
|
||||
|
||||
return beaconMessage;
|
||||
} catch (IllegalArgumentException exception) {
|
||||
System.out.println(
|
||||
"[BeaconTask, Warning]: Beacon for "
|
||||
+ categoryDescription
|
||||
+ " was not sent because the resolved text contains "
|
||||
+ actualLength
|
||||
+ " characters; maximum is "
|
||||
+ ChatController.MAX_BEACON_TEXT_LENGTH
|
||||
+ "."
|
||||
+ " was not queued: "
|
||||
+ exception.getMessage()
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
ChatMessage beaconMessage = new ChatMessage();
|
||||
beaconMessage.setMessageText(
|
||||
"MSG|" + categoryNumber + "|0|" + resolvedText + "|0|"
|
||||
);
|
||||
beaconMessage.setMessageDirectedToServer(true);
|
||||
return beaconMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,6 +214,9 @@ public class BeaconTask extends TimerTask {
|
||||
information,
|
||||
criticalState
|
||||
);
|
||||
callbackToController.onThreadStatus(THREAD_NICKNAME, stateMessage);
|
||||
callbackToController.onThreadStatus(
|
||||
THREAD_NICKNAME,
|
||||
stateMessage
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Formats local DX Cluster spots using the fixed-column layout emitted by
|
||||
* DXSpider and accepted by common logging programs.
|
||||
*/
|
||||
final class DXClusterSpotFormatter {
|
||||
|
||||
/** Length of the DX Cluster line before BEL and CRLF framing. */
|
||||
/* package */
|
||||
static final int LINE_LENGTH = 75;
|
||||
/** One-based column in which the spotted callsign starts. */
|
||||
/* package */
|
||||
static final int DX_CALL_COLUMN = 27;
|
||||
/** Width of the fixed comment field. */
|
||||
/* package */
|
||||
static final int COMMENT_LENGTH = 30;
|
||||
/** One-based column in which the UTC time starts. */
|
||||
/* package */
|
||||
static final int TIME_COLUMN = 71;
|
||||
|
||||
/** Zero-based exclusive end position of the frequency field. */
|
||||
private static final int FREQUENCY_END = 24;
|
||||
/** Maximum width of the spotted callsign field. */
|
||||
private static final int DX_CALL_LENGTH = 12;
|
||||
/** Required width of the HHMMZ time field. */
|
||||
private static final int TIME_LENGTH = 5;
|
||||
/** Minimum separator width between spotter and frequency. */
|
||||
private static final int MIN_FREQUENCY_GAP = 1;
|
||||
/** Wire framing appended to every formatted line. */
|
||||
private static final String PAYLOAD_SUFFIX = "\u0007\u0007\r\n";
|
||||
|
||||
private DXClusterSpotFormatter() {
|
||||
}
|
||||
|
||||
/** Builds the fixed 75-character payload line without wire framing. */
|
||||
/* package */
|
||||
static String formatLine(
|
||||
final String spotterCallSign,
|
||||
final String frequency,
|
||||
final String dxCallSign,
|
||||
final String comment,
|
||||
final String time
|
||||
) {
|
||||
final String spotter = requireValue(
|
||||
spotterCallSign,
|
||||
"spotter callsign"
|
||||
)
|
||||
.toUpperCase(Locale.ROOT);
|
||||
final String frequencyValue = requireValue(frequency, "frequency");
|
||||
final String dxCall = requireValue(dxCallSign, "DX callsign")
|
||||
.toUpperCase(Locale.ROOT);
|
||||
final String timeValue = requireValue(time, "time");
|
||||
|
||||
validateDxCall(dxCall);
|
||||
validateTime(timeValue);
|
||||
|
||||
final String prefix = "DX de " + spotter + ":";
|
||||
final int frequencyPadding = calculateFrequencyPadding(
|
||||
prefix,
|
||||
frequencyValue
|
||||
);
|
||||
final String normalizedComment = normalizeComment(comment);
|
||||
|
||||
final String line = prefix
|
||||
+ " ".repeat(frequencyPadding)
|
||||
+ frequencyValue
|
||||
+ " "
|
||||
+ padRight(dxCall, DX_CALL_LENGTH)
|
||||
+ " "
|
||||
+ padRight(normalizedComment, COMMENT_LENGTH)
|
||||
+ " "
|
||||
+ timeValue;
|
||||
|
||||
if (line.length() != LINE_LENGTH) {
|
||||
throw new IllegalStateException(
|
||||
"DX Cluster formatter produced "
|
||||
+ line.length()
|
||||
+ " characters instead of "
|
||||
+ LINE_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/** Builds one complete ASCII spot payload including BEL and CRLF framing. */
|
||||
/* package */
|
||||
static byte[] formatPayload(
|
||||
final String spotterCallSign,
|
||||
final String frequency,
|
||||
final String dxCallSign,
|
||||
final String comment,
|
||||
final String time
|
||||
) {
|
||||
return (formatLine(
|
||||
spotterCallSign,
|
||||
frequency,
|
||||
dxCallSign,
|
||||
comment,
|
||||
time
|
||||
) + PAYLOAD_SUFFIX).getBytes(StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private static void validateDxCall(final String dxCall) {
|
||||
if (dxCall.length() > DX_CALL_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"DX callsign exceeds 12 characters: " + dxCall
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateTime(final String time) {
|
||||
if (time.length() != TIME_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"DX Cluster time must contain exactly five characters"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static int calculateFrequencyPadding(
|
||||
final String prefix,
|
||||
final String frequency
|
||||
) {
|
||||
final int padding = FREQUENCY_END
|
||||
- prefix.length()
|
||||
- frequency.length();
|
||||
|
||||
if (padding < MIN_FREQUENCY_GAP) {
|
||||
throw new IllegalArgumentException(
|
||||
"Spotter callsign and frequency do not fit the DX Cluster prefix"
|
||||
);
|
||||
}
|
||||
|
||||
return padding;
|
||||
}
|
||||
|
||||
private static String normalizeComment(final String comment) {
|
||||
final String normalized = comment == null ? "" : comment.trim();
|
||||
|
||||
return normalized.length() > COMMENT_LENGTH
|
||||
? normalized.substring(0, COMMENT_LENGTH)
|
||||
: normalized;
|
||||
}
|
||||
|
||||
private static String requireValue(
|
||||
final String value,
|
||||
final String fieldName
|
||||
) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"DX Cluster " + fieldName + " is missing"
|
||||
);
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private static String padRight(final String value, final int length) {
|
||||
return value + " ".repeat(length - value.length());
|
||||
}
|
||||
}
|
||||
@@ -152,6 +152,7 @@ public class DXClusterThreadPooledServer implements Runnable {
|
||||
public boolean broadcastSingleDXClusterEntryToLoggers(
|
||||
ChatMember chatMember
|
||||
) {
|
||||
final byte[] clusterPayload;
|
||||
final String clusterMessage;
|
||||
|
||||
try {
|
||||
@@ -162,24 +163,27 @@ public class DXClusterThreadPooledServer implements Runnable {
|
||||
.getNotify_optionalFrequencyPrefix()
|
||||
);
|
||||
|
||||
clusterMessage =
|
||||
"DX de "
|
||||
+ chatController
|
||||
clusterPayload = DXClusterSpotFormatter.formatPayload(
|
||||
chatController
|
||||
.getChatPreferences()
|
||||
.getNotify_DXCSrv_SpottersCallSign()
|
||||
.getValue()
|
||||
+ ": "
|
||||
+ frequency
|
||||
+ " "
|
||||
+ chatMember.getCallSign().toUpperCase()
|
||||
+ " "
|
||||
+ chatMember.getQra().toUpperCase()
|
||||
+ " "
|
||||
+ new Utils4KST()
|
||||
.getValue(),
|
||||
frequency,
|
||||
chatMember.getCallSign(),
|
||||
chatMember.getQra(),
|
||||
new Utils4KST()
|
||||
.time_generateCurrenthhmmZTimeStringForClusterMessage()
|
||||
+ ((char) 7)
|
||||
+ ((char) 7)
|
||||
+ "\r\n";
|
||||
);
|
||||
clusterMessage = new String(
|
||||
clusterPayload,
|
||||
StandardCharsets.US_ASCII
|
||||
);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
LOGGER.log(
|
||||
Level.WARNING,
|
||||
"DX Cluster spot rejected: " + exception.getMessage()
|
||||
);
|
||||
return false;
|
||||
} catch (Exception exception) {
|
||||
LOGGER.log(
|
||||
Level.SEVERE,
|
||||
@@ -204,11 +208,7 @@ public class DXClusterThreadPooledServer implements Runnable {
|
||||
|
||||
try {
|
||||
OutputStream output = socket.getOutputStream();
|
||||
output.write(
|
||||
clusterMessage.getBytes(
|
||||
StandardCharsets.US_ASCII
|
||||
)
|
||||
);
|
||||
output.write(clusterPayload);
|
||||
output.flush();
|
||||
deliveredClients++;
|
||||
} catch (IOException exception) {
|
||||
@@ -360,4 +360,4 @@ class DXClusterServerWorkerRunnable implements Runnable {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import kst4contest.model.ChatMember;
|
||||
import kst4contest.model.ChatPreferences;
|
||||
|
||||
public class DXClusterThreadPooledServerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
ChatController client = new ChatController();
|
||||
ChatPreferences testPreferences = new ChatPreferences();
|
||||
testPreferences.setStn_loginCallSign("DM5M");
|
||||
|
||||
client.setChatPreferences(testPreferences);
|
||||
DXClusterThreadPooledServer dxClusterServer = new DXClusterThreadPooledServer(8000, client, client);
|
||||
|
||||
new Thread(dxClusterServer).start();
|
||||
|
||||
|
||||
try {
|
||||
Thread.sleep(10 * 1000);
|
||||
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>ready.....go!");
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
ChatMember test = new ChatMember();
|
||||
test.setCallSign("DL5ASG");
|
||||
test.setQra("JO51HK");
|
||||
test.setFrequency(new SimpleStringProperty("144776.0"));
|
||||
|
||||
dxClusterServer.broadcastSingleDXClusterEntryToLoggers(test);
|
||||
|
||||
|
||||
// try {
|
||||
// Thread.sleep(20 * 3333);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// System.out.println("Stopping Server");
|
||||
// server.stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import kst4contest.model.ChatMember;
|
||||
|
||||
/**
|
||||
* Validated QSO state shared by external logger listeners and the controller.
|
||||
*/
|
||||
public final class ExternalLoggedQso {
|
||||
private final String callSign;
|
||||
private final LoggedQsoBand band;
|
||||
private final String locator;
|
||||
private final String source;
|
||||
|
||||
private ExternalLoggedQso(
|
||||
String callSign,
|
||||
LoggedQsoBand band,
|
||||
String locator,
|
||||
String source
|
||||
) {
|
||||
this.callSign = callSign;
|
||||
this.band = band;
|
||||
this.locator = locator;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a QSO only when the logger supplied a usable callsign. Missing band
|
||||
* and locator values remain unavailable and never receive guessed defaults.
|
||||
*
|
||||
* @param rawCallSign callsign received from the logger
|
||||
* @param band normalized band or {@code null}
|
||||
* @param rawLocator locator received from the logger or {@code null}
|
||||
* @param source logger name used for persistence diagnostics
|
||||
* @return validated QSO, or an empty value for a missing callsign
|
||||
*/
|
||||
public static Optional<ExternalLoggedQso> create(
|
||||
String rawCallSign,
|
||||
LoggedQsoBand band,
|
||||
String rawLocator,
|
||||
String source
|
||||
) {
|
||||
if (rawCallSign == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String normalizedCallSign = rawCallSign.trim();
|
||||
if (normalizedCallSign.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String normalizedLocator = WorkedGrossFieldCache.extractLocator6(rawLocator);
|
||||
return Optional.of(new ExternalLoggedQso(
|
||||
normalizedCallSign,
|
||||
band,
|
||||
normalizedLocator,
|
||||
source
|
||||
));
|
||||
}
|
||||
|
||||
public String getCallSign() {
|
||||
return callSign;
|
||||
}
|
||||
|
||||
public LoggedQsoBand getBand() {
|
||||
return band;
|
||||
}
|
||||
|
||||
public String getLocator() {
|
||||
return locator;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the database representation of this QSO.
|
||||
*
|
||||
* @return chat-member state with global and optional band Worked flags
|
||||
*/
|
||||
public ChatMember toWorkedChatMember() {
|
||||
ChatMember workedCall = new ChatMember();
|
||||
workedCall.setCallSign(callSign);
|
||||
workedCall.setWorked(true);
|
||||
if (band != null) {
|
||||
band.applyWorkedFlag(workedCall);
|
||||
}
|
||||
if (locator != null) {
|
||||
workedCall.setQra(locator);
|
||||
}
|
||||
return workedCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this QSO to one active callsign variant.
|
||||
*
|
||||
* @param member active variant with the same base callsign
|
||||
*/
|
||||
public void applyToActiveMember(ChatMember member) {
|
||||
if (member == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
member.setWorked(true);
|
||||
if (band != null) {
|
||||
band.applyWorkedFlag(member);
|
||||
}
|
||||
if (locator != null
|
||||
&& (member.getQra() == null
|
||||
|| member.getQra().isBlank()
|
||||
|| "unknown".equalsIgnoreCase(member.getQra()))) {
|
||||
member.setQra(locator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import kst4contest.model.Band;
|
||||
import kst4contest.model.ChatMember;
|
||||
|
||||
/**
|
||||
* Normalized band of a QSO received from an external logger.
|
||||
*
|
||||
* <p>Logger protocols use different representations for the same amateur-radio
|
||||
* band. UCXLog-compatible XML may contain a frequency-like number or a metre /
|
||||
* centimetre label, while Win-Test uses its own numeric band IDs. Resolving the
|
||||
* raw value once prevents the Worked flag and gross-field update from interpreting
|
||||
* the same packet differently.</p>
|
||||
*/
|
||||
public enum LoggedQsoBand {
|
||||
BAND_50(Band.B_50),
|
||||
BAND_70(Band.B_70),
|
||||
BAND_144(Band.B_144),
|
||||
BAND_432(Band.B_432),
|
||||
BAND_1296(Band.B_1296),
|
||||
BAND_2320(Band.B_2320),
|
||||
BAND_3400(Band.B_3400),
|
||||
BAND_5760(Band.B_5760),
|
||||
BAND_10G(Band.B_10G),
|
||||
BAND_24G(Band.B_24G),
|
||||
BAND_47G(null),
|
||||
BAND_76G(null);
|
||||
|
||||
private final Band projectBand;
|
||||
|
||||
LoggedQsoBand(Band projectBand) {
|
||||
this.projectBand = projectBand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves aliases used by UCXLog, N1MM+, DXLog.net, QARTest and compatible
|
||||
* loggers. The input is trimmed and case-normalized exactly once here.
|
||||
*
|
||||
* @param rawBand band value received in the XML packet
|
||||
* @return normalized band, or {@code null} for a missing or unknown value
|
||||
*/
|
||||
public static LoggedQsoBand fromLoggerValue(String rawBand) {
|
||||
if (rawBand == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedBand = rawBand.trim().toLowerCase(Locale.ROOT);
|
||||
return switch (normalizedBand) {
|
||||
case "50", "6m" -> BAND_50;
|
||||
case "70", "4m" -> BAND_70;
|
||||
case "144", "2m" -> BAND_144;
|
||||
case "432", "70cm" -> BAND_432;
|
||||
case "1240", "1296", "23cm" -> BAND_1296;
|
||||
case "2300", "2320", "13cm" -> BAND_2320;
|
||||
case "3400", "9cm" -> BAND_3400;
|
||||
case "5600", "5760", "6cm" -> BAND_5760;
|
||||
case "10g", "10368", "3cm" -> BAND_10G;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the established Win-Test ADDQSO band IDs. IDs 22 and 23 retain
|
||||
* their existing Worked flags even though the project Band enum has no 47 GHz
|
||||
* or 76 GHz value for gross-field storage.
|
||||
*
|
||||
* @param rawBandId Win-Test band ID
|
||||
* @return normalized band, or {@code null} for a missing or unknown ID
|
||||
*/
|
||||
public static LoggedQsoBand fromWinTestBandId(String rawBandId) {
|
||||
if (rawBandId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedBandId = rawBandId.trim();
|
||||
return switch (normalizedBandId) {
|
||||
case "10" -> BAND_50;
|
||||
case "11" -> BAND_70;
|
||||
case "12" -> BAND_144;
|
||||
case "14" -> BAND_432;
|
||||
case "16" -> BAND_1296;
|
||||
case "17" -> BAND_2320;
|
||||
case "18" -> BAND_3400;
|
||||
case "19" -> BAND_5760;
|
||||
case "20" -> BAND_10G;
|
||||
case "21" -> BAND_24G;
|
||||
case "22" -> BAND_47G;
|
||||
case "23" -> BAND_76G;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the common Band value used by worked-grid persistence.
|
||||
*
|
||||
* @return project band, or {@code null} where no matching Band value exists
|
||||
*/
|
||||
public Band getProjectBand() {
|
||||
return projectBand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the established per-band Worked flag to a chat member.
|
||||
*
|
||||
* @param member worked station or active callsign variant
|
||||
*/
|
||||
public void applyWorkedFlag(ChatMember member) {
|
||||
if (member == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this) {
|
||||
case BAND_50 -> member.setWorked50(true);
|
||||
case BAND_70 -> member.setWorked70(true);
|
||||
case BAND_144 -> member.setWorked144(true);
|
||||
case BAND_432 -> member.setWorked432(true);
|
||||
case BAND_1296 -> member.setWorked1240(true);
|
||||
case BAND_2320 -> member.setWorked2300(true);
|
||||
case BAND_3400 -> member.setWorked3400(true);
|
||||
case BAND_5760 -> member.setWorked5600(true);
|
||||
case BAND_10G -> member.setWorked10G(true);
|
||||
case BAND_24G -> member.setWorked24G(true);
|
||||
case BAND_47G -> member.setWorked47G(true);
|
||||
case BAND_76G -> member.setWorked76G(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ import kst4contest.ApplicationConstants;
|
||||
import kst4contest.locatorUtils.DirectionUtils;
|
||||
import kst4contest.locatorUtils.Location;
|
||||
import kst4contest.model.*;
|
||||
import kst4contest.logic.FrequencyTextParser;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.function.LongPredicate;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -36,6 +39,11 @@ public class MessageBusManagementThread extends Thread {
|
||||
private PrintWriter writer;
|
||||
// private Socket socket;
|
||||
private ChatController client;
|
||||
|
||||
private final long connectionSessionId;
|
||||
private final LinkedBlockingQueue<ChatMessage> receiveQueue;
|
||||
private final LongPredicate connectionSessionIsActive;
|
||||
|
||||
// private File fileLogRAW;
|
||||
// private TimerTask userActualizationTask; // Is used as a temporary userout-print
|
||||
// private TimerTask userActualizationTask; //kst4contest.test 4 23001
|
||||
@@ -49,7 +57,7 @@ public class MessageBusManagementThread extends Thread {
|
||||
/*
|
||||
* Frequency formats handled by the smart parser:
|
||||
*
|
||||
* Group 1: full frequencies, for example 144.210 or 10368.100
|
||||
* Group 1: full frequencies, for example 144.210, 144210 or 10368100
|
||||
* Group 2: relative frequencies with a separator, for example .210 or ,210
|
||||
* Group 3: bare three-digit values, for example 210
|
||||
*
|
||||
@@ -58,7 +66,7 @@ public class MessageBusManagementThread extends Thread {
|
||||
* would be converted into plausible but incorrect frequencies.
|
||||
*/
|
||||
private static final Pattern SMART_FREQUENCY_PATTERN = Pattern.compile(
|
||||
"(?<![\\d])(\\d{3,5}[.,]\\d{1,3}(?:[.,]\\d{1,3})?)(?![\\d])"
|
||||
"(?<![A-Z0-9])(\\d{2,5}[.,]\\d{1,3}(?:[.,]\\d{1,3})?|\\d{5,8})(?![A-Z0-9])"
|
||||
+ "|(?<![\\d])([.,]\\d{3}(?:[.,]\\d{1,3})?)(?![\\d])"
|
||||
+ "|(?<=\\s|^)(\\d{3})(?=\\s|$)"
|
||||
);
|
||||
@@ -117,9 +125,22 @@ public class MessageBusManagementThread extends Thread {
|
||||
}
|
||||
|
||||
public MessageBusManagementThread(ChatController client, ThreadStatusCallback callBack) {
|
||||
this(client, callBack, 0L, client.getMessageRXBus(), ignored -> true);
|
||||
}
|
||||
|
||||
public MessageBusManagementThread(
|
||||
ChatController client,
|
||||
ThreadStatusCallback callBack,
|
||||
long connectionSessionId,
|
||||
LinkedBlockingQueue<ChatMessage> receiveQueue,
|
||||
LongPredicate connectionSessionIsActive
|
||||
) {
|
||||
|
||||
this.callBackToController = callBack;
|
||||
this.client = client;
|
||||
this.connectionSessionId = connectionSessionId;
|
||||
this.receiveQueue = receiveQueue;
|
||||
this.connectionSessionIsActive = connectionSessionIsActive;
|
||||
|
||||
ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "initialized", false);
|
||||
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
|
||||
@@ -258,7 +279,7 @@ public class MessageBusManagementThread extends Thread {
|
||||
* @param message message whose text is inspected
|
||||
* @param prefs preferences containing the global fallback band
|
||||
*/
|
||||
private void smartFrequencyExtraction(ChatMessage message, ChatPreferences prefs) {
|
||||
/* package */ void smartFrequencyExtraction(ChatMessage message, ChatPreferences prefs) {
|
||||
if (message == null || message.getMessageText() == null) {
|
||||
return;
|
||||
}
|
||||
@@ -319,16 +340,28 @@ public class MessageBusManagementThread extends Thread {
|
||||
|
||||
try {
|
||||
String reconstructed =
|
||||
candidateBand.getPrefix() + "." + foundRaw;
|
||||
double candidateFrequency = Double.parseDouble(
|
||||
normalizeFrequencyString(reconstructed)
|
||||
);
|
||||
candidateBand.getPrefix()
|
||||
+ "."
|
||||
+ foundRaw;
|
||||
|
||||
if (candidateBand.isPlausible(candidateFrequency)
|
||||
FrequencyTextParser.DetectedFrequency detectedFrequency =
|
||||
FrequencyTextParser.parseExplicitFrequency(
|
||||
reconstructed
|
||||
);
|
||||
|
||||
if (detectedFrequency != null
|
||||
&& detectedFrequency.getBand() == candidateBand
|
||||
&& info.timestampEpoch > bestTimestamp) {
|
||||
finalDetectedFrequency = candidateFrequency;
|
||||
finalDetectedBand = candidateBand;
|
||||
bestTimestamp = info.timestampEpoch;
|
||||
|
||||
finalDetectedFrequency =
|
||||
detectedFrequency.getFrequencyMHz();
|
||||
|
||||
finalDetectedBand =
|
||||
candidateBand;
|
||||
|
||||
bestTimestamp =
|
||||
info.timestampEpoch;
|
||||
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Try the next known band.
|
||||
@@ -357,27 +390,40 @@ public class MessageBusManagementThread extends Thread {
|
||||
|
||||
try {
|
||||
String reconstructed =
|
||||
fallbackBand.getPrefix() + "." + foundRaw;
|
||||
double candidateFrequency = Double.parseDouble(
|
||||
normalizeFrequencyString(reconstructed)
|
||||
);
|
||||
fallbackBand.getPrefix()
|
||||
+ "."
|
||||
+ foundRaw;
|
||||
|
||||
if (fallbackBand.isPlausible(candidateFrequency)) {
|
||||
finalDetectedFrequency = candidateFrequency;
|
||||
finalDetectedBand = fallbackBand;
|
||||
FrequencyTextParser.DetectedFrequency detectedFrequency =
|
||||
FrequencyTextParser.parseExplicitFrequency(
|
||||
reconstructed
|
||||
);
|
||||
|
||||
if (detectedFrequency != null
|
||||
&& detectedFrequency.getBand() == fallbackBand) {
|
||||
|
||||
finalDetectedFrequency =
|
||||
detectedFrequency.getFrequencyMHz();
|
||||
|
||||
finalDetectedBand =
|
||||
fallbackBand;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
// The matched value cannot be converted into a frequency.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
finalDetectedFrequency = Double.parseDouble(
|
||||
normalizeFrequencyString(foundRaw)
|
||||
);
|
||||
finalDetectedBand = Band.fromFrequency(finalDetectedFrequency);
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Continue with the next possible match in the message.
|
||||
FrequencyTextParser.DetectedFrequency detectedFrequency =
|
||||
FrequencyTextParser.parseExplicitFrequency(
|
||||
foundRaw
|
||||
);
|
||||
|
||||
if (detectedFrequency != null) {
|
||||
finalDetectedFrequency =
|
||||
detectedFrequency.getFrequencyMHz();
|
||||
|
||||
finalDetectedBand =
|
||||
detectedFrequency.getBand();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,22 +508,22 @@ public class MessageBusManagementThread extends Thread {
|
||||
* Example: "144.210.10" -> "144.21010"
|
||||
* Example: "144.210" -> "144.210"
|
||||
*/
|
||||
private String normalizeFrequencyString(String rawInput) {
|
||||
// Input is already guaranteed to have only dots as separators (commas replaced earlier)
|
||||
|
||||
int firstDotIndex = rawInput.indexOf(".");
|
||||
|
||||
if (firstDotIndex != -1) {
|
||||
// Check if there are more dots after the first one
|
||||
String decimalPart = rawInput.substring(firstDotIndex + 1);
|
||||
if (decimalPart.contains(".")) {
|
||||
// Remove all subsequent dots to make it a valid double
|
||||
decimalPart = decimalPart.replace(".", "");
|
||||
return rawInput.substring(0, firstDotIndex) + "." + decimalPart;
|
||||
}
|
||||
}
|
||||
return rawInput;
|
||||
}
|
||||
// private String normalizeFrequencyString(String rawInput) {
|
||||
// // Input is already guaranteed to have only dots as separators (commas replaced earlier)
|
||||
//
|
||||
// int firstDotIndex = rawInput.indexOf(".");
|
||||
//
|
||||
// if (firstDotIndex != -1) {
|
||||
// // Check if there are more dots after the first one
|
||||
// String decimalPart = rawInput.substring(firstDotIndex + 1);
|
||||
// if (decimalPart.contains(".")) {
|
||||
// // Remove all subsequent dots to make it a valid double
|
||||
// decimalPart = decimalPart.replace(".", "");
|
||||
// return rawInput.substring(0, firstDotIndex) + "." + decimalPart;
|
||||
// }
|
||||
// }
|
||||
// return rawInput;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
@@ -496,7 +542,8 @@ public class MessageBusManagementThread extends Thread {
|
||||
|
||||
messageToProcess.setMessageText(reduce);
|
||||
|
||||
if (messageToProcess.getMessageText().isEmpty()) {
|
||||
if (messageToProcess.getMessageText() == null
|
||||
|| messageToProcess.getMessageText().isEmpty()) {
|
||||
// System.out.println("[MSGBUSMGTT:] ###################### no processable data");
|
||||
} else {
|
||||
|
||||
@@ -728,6 +775,50 @@ public class MessageBusManagementThread extends Thread {
|
||||
return fallbackReceiver;
|
||||
}
|
||||
|
||||
private boolean validateInboundUserFrame(String[] fields) {
|
||||
if (fields == null || fields.length < 6) {
|
||||
logRejectedInboundUserFrame(
|
||||
"Ignoring truncated ON4KST user frame", fields);
|
||||
return false;
|
||||
}
|
||||
if (fields[2] == null || fields[2].isBlank()) {
|
||||
logRejectedInboundUserFrame(
|
||||
"Ignoring ON4KST user frame with empty callsign", fields);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
On4KstProtocol.category(Integer.parseInt(fields[1]));
|
||||
On4KstProtocol.locator(fields[4]);
|
||||
Integer.parseInt(fields[5]);
|
||||
return true;
|
||||
} catch (IllegalArgumentException invalidUser) {
|
||||
logRejectedInboundUserFrame(
|
||||
"Ignoring malformed ON4KST user '" + fields[2]
|
||||
+ "': " + invalidUser.getMessage(), fields);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a rejected user frame without writing the complete raw frame or user
|
||||
* name to the diagnostic log.
|
||||
*
|
||||
* <p>The opcode, category and callsign are sufficient to identify the offending
|
||||
* list position. Omitting the remaining fields avoids unnecessary disclosure of
|
||||
* free-form profile text.</p>
|
||||
*/
|
||||
private void logRejectedInboundUserFrame(String reason, String[] fields) {
|
||||
String opcode = fields != null && fields.length > 0 ? fields[0] : "UNKNOWN";
|
||||
String category = fields != null && fields.length > 1 ? fields[1] : "UNKNOWN";
|
||||
String callsign = fields != null && fields.length > 2 ? fields[2] : "UNKNOWN";
|
||||
client.onOn4KstConnectionWarning(
|
||||
reason + "; opcode=" + opcode
|
||||
+ ", category=" + category
|
||||
+ ", callsign=" + callsign
|
||||
+ ", fieldCount=" + (fields == null ? 0 : fields.length));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes received messages via port 23001 (improved telnet Interface)
|
||||
*
|
||||
@@ -782,23 +873,39 @@ public class MessageBusManagementThread extends Thread {
|
||||
* here we have a helper list for identifying questions for my qrg which can be autoanswered later
|
||||
*/
|
||||
|
||||
if (messageToProcess.getMessageText().isEmpty()) {
|
||||
// System.out.println("[MSGBUSMGTT:] no processable data");
|
||||
|
||||
if (messageToProcess.getMessageText() == null
|
||||
|| messageToProcess.getMessageText().isEmpty()) {
|
||||
// No processable data.
|
||||
} else {
|
||||
|
||||
if (messageToProcess.getMessageText().contains(SRVR_LOGSTAT)) {
|
||||
String logstatMessage[];
|
||||
logstatMessage = messageToProcess.getMessageText().split("\\|");
|
||||
if (logstatMessage[1].contains(SRVR_LOGINOK)) {
|
||||
this.client.setConnectedAndLoggedIn(true);
|
||||
} else {
|
||||
this.client.setConnectedAndNOTLoggedIn(true);
|
||||
this.client.setConnectedAndLoggedIn(false);
|
||||
}
|
||||
if (On4KstProtocol.isConnectionProbeResponse(
|
||||
messageToProcess.getMessageText())) {
|
||||
// DXQ is the internal response to the active connection probe.
|
||||
// Liveness was already recorded by the session manager.
|
||||
return;
|
||||
}
|
||||
|
||||
String splittedMessageLine[] = messageToProcess.getMessageText().split("\\|");
|
||||
if (messageToProcess.getMessageText().startsWith(SRVR_LOGSTAT + "|")) {
|
||||
String[] logstatMessage =
|
||||
messageToProcess.getMessageText().split("\\|", -1);
|
||||
this.client.onOn4KstLogstat(
|
||||
connectionSessionId,
|
||||
logstatMessage);
|
||||
}
|
||||
|
||||
String[] splittedMessageLine =
|
||||
messageToProcess.getMessageText().split("\\|");
|
||||
|
||||
String opcode = splittedMessageLine.length == 0
|
||||
? ""
|
||||
: splittedMessageLine[0];
|
||||
|
||||
if ((INITIALUSERLISTENTRY.equals(opcode)
|
||||
|| USERENTEREDCHAT.equals(opcode)
|
||||
|| USERENTEREDCHAT2.equals(opcode))
|
||||
&& !validateInboundUserFrame(splittedMessageLine)) {
|
||||
return;
|
||||
}
|
||||
// String splittedMessageLine[] = messageToProcess.getMessageText().split("\\|");
|
||||
|
||||
/**
|
||||
* Initializes the Userlist if entry fits UA0
|
||||
@@ -806,7 +913,7 @@ public class MessageBusManagementThread extends Thread {
|
||||
*
|
||||
*
|
||||
*/
|
||||
if (splittedMessageLine[0].contains(INITIALUSERLISTENTRY)) {
|
||||
if (splittedMessageLine[0].equals(INITIALUSERLISTENTRY)) {
|
||||
// System.out.println("MSGBUS: User detected");
|
||||
|
||||
ChatMember newMember = new ChatMember();
|
||||
@@ -827,7 +934,9 @@ public class MessageBusManagementThread extends Thread {
|
||||
|
||||
|
||||
if (!client.getChatPreferences().getStn_loginCallSign().equals(newMember.getCallSign())) {
|
||||
this.client.addOrUpdateActiveChatMember(newMember); // the own call will not be in the list
|
||||
this.client.stageInitialOn4KstChatMember(
|
||||
connectionSessionId,
|
||||
newMember);
|
||||
// this.client.getReachabilityService().ensureAutoTropoMarginCalculated(newMember);
|
||||
// Reachability is calculated on demand only: map click, selected station, or manual request.
|
||||
}
|
||||
@@ -851,7 +960,8 @@ public class MessageBusManagementThread extends Thread {
|
||||
* UA2|2|W5ADD|Parker|EM40WL|2|
|
||||
*
|
||||
*/
|
||||
if (splittedMessageLine[0].contains(USERENTEREDCHAT) || splittedMessageLine[0].contains(USERENTEREDCHAT2)) {
|
||||
if (splittedMessageLine[0].equals(USERENTEREDCHAT)
|
||||
|| splittedMessageLine[0].equals(USERENTEREDCHAT2)) {
|
||||
// System.out.println("MSGBUS: User detected");
|
||||
|
||||
|
||||
@@ -1063,16 +1173,22 @@ public class MessageBusManagementThread extends Thread {
|
||||
// }
|
||||
// }
|
||||
|
||||
// ==== Unified auto-answer (generic + QRG) with ping-pong guard and per-remote cooldown ====
|
||||
// ==== Unified auto-answer (generic + QRG) with ping-pong guard
|
||||
// and per-remote cooldown ====
|
||||
final String incomingText = newMessageArrived.getMessageText();
|
||||
final String incomingLower = (incomingText == null) ? "" : incomingText.toLowerCase(Locale.ROOT);
|
||||
final String incomingLower =
|
||||
(incomingText == null)
|
||||
? ""
|
||||
: incomingText.toLowerCase(Locale.ROOT);
|
||||
|
||||
// Never answer another automatically generated message.
|
||||
// Never answer another automatically generated message.
|
||||
if (!isAutoMessage(newMessageArrived)) {
|
||||
|
||||
boolean qrgRequested = false;
|
||||
|
||||
if (this.client.getChatPreferences().isMessageHandling_autoAnswerToQRGRequestEnabled()) {
|
||||
if (this.client.getChatPreferences()
|
||||
.isMessageHandling_autoAnswerToQRGRequestEnabled()) {
|
||||
|
||||
for (String lookForQRGString : qrgQuestionTexts) {
|
||||
if (incomingLower.contains(lookForQRGString)) {
|
||||
qrgRequested = true;
|
||||
@@ -1081,36 +1197,47 @@ public class MessageBusManagementThread extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
boolean genericEnabled = this.client.getChatPreferences().isMsgHandling_autoAnswerEnabled();
|
||||
boolean genericEnabled =
|
||||
this.client.getChatPreferences()
|
||||
.isMsgHandling_autoAnswerEnabled();
|
||||
|
||||
// A QRG reply takes precedence over the generic reply.
|
||||
String payload = null;
|
||||
String automaticAnswerText = buildAutoAnswerMessageText(
|
||||
newMessageArrived,
|
||||
qrgRequested,
|
||||
genericEnabled
|
||||
);
|
||||
|
||||
if (qrgRequested) {
|
||||
payload = "QRG is: " + getAutoAnswerQrgForCategory(newMessageArrived.getChatCategory());
|
||||
} else if (genericEnabled) {
|
||||
|
||||
payload = this.client.getChatPreferences().getMessageHandling_autoAnswerTextMainCat();
|
||||
}
|
||||
|
||||
// Apply the cooldown only when this client is about to send a reply.
|
||||
if (payload != null && isAutoAnswerAllowedNow(newMessageArrived)) {
|
||||
/*
|
||||
* Invalid or incomplete replies are rejected before the cooldown
|
||||
* is checked or updated. A missing QRG must therefore not suppress
|
||||
* a later valid reply.
|
||||
*/
|
||||
if (automaticAnswerText != null
|
||||
&& isAutoAnswerAllowedNow(newMessageArrived)) {
|
||||
|
||||
ChatMessage automaticAnswer = new ChatMessage();
|
||||
ChatMember itsMe = new ChatMember();
|
||||
itsMe.setCallSign(this.client.getChatPreferences().getStn_loginCallSign());
|
||||
|
||||
itsMe.setCallSign(
|
||||
this.client.getChatPreferences()
|
||||
.getStn_loginCallSign()
|
||||
);
|
||||
|
||||
automaticAnswer.setSender(itsMe);
|
||||
automaticAnswer.setReceiver(newMessageArrived.getSender());
|
||||
automaticAnswer.setChatCategory(newMessageArrived.getChatCategory());
|
||||
|
||||
// The fixed prefix prevents automatic clients from answering each other.
|
||||
automaticAnswer.setMessageText("/CQ " + newMessageArrived.getSender().getCallSign()
|
||||
+ " " + AUTOANSWER_PREFIX + " " + payload);
|
||||
automaticAnswer.setReceiver(
|
||||
newMessageArrived.getSender()
|
||||
);
|
||||
automaticAnswer.setChatCategory(
|
||||
newMessageArrived.getChatCategory()
|
||||
);
|
||||
automaticAnswer.setMessageText(automaticAnswerText);
|
||||
|
||||
this.client.getMessageTXBus().add(automaticAnswer);
|
||||
|
||||
// Record only locally generated replies, not the later server echo.
|
||||
/*
|
||||
* Record the cooldown only after a complete and locally
|
||||
* validated reply has been placed in the transmit queue.
|
||||
*/
|
||||
markLocalAutoAnswerSent(newMessageArrived);
|
||||
}
|
||||
}
|
||||
@@ -1614,18 +1741,29 @@ public class MessageBusManagementThread extends Thread {
|
||||
/**
|
||||
* Userinfo-update: UE|2|22562|
|
||||
*/
|
||||
if (splittedMessageLine[0].contains(SRVR_USERLISTEND)) {
|
||||
if (SRVR_USERLISTEND.equals(opcode)) {
|
||||
if (splittedMessageLine.length < 2) {
|
||||
System.out.println(
|
||||
"[MSGBUSMGT, Warning:] Ignoring malformed UE frame: "
|
||||
+ messageToProcess.getMessageText());
|
||||
return;
|
||||
}
|
||||
|
||||
// No worthy information, count of users
|
||||
} else
|
||||
this.client.onOn4KstInitialUserListCompleted(
|
||||
connectionSessionId,
|
||||
util_getChatCategoryByCategoryNrString(
|
||||
splittedMessageLine[1]));
|
||||
|
||||
if (splittedMessageLine[0].contains(SRVR_DXCEND)) {
|
||||
} else if (SRVR_DXCEND.equals(opcode)) {
|
||||
|
||||
// No worthy information, count of users
|
||||
} else
|
||||
// DF marks the end of the initial DX-cluster data.
|
||||
// The frame contains no data that needs to be published.
|
||||
|
||||
} else if (SRVR_COMMUNICATIONK.equals(opcode)) {
|
||||
|
||||
// CK is a regular server delimiter/acknowledgement.
|
||||
// It is intentionally accepted without further processing.
|
||||
|
||||
if (splittedMessageLine[0].contains(SRVR_COMMUNICATIONK)) {
|
||||
// No worthy information, end of srvrmsgs
|
||||
} else
|
||||
|
||||
//-> LOGSTAT|114|Wrong password!|
|
||||
@@ -1709,14 +1847,14 @@ public class MessageBusManagementThread extends Thread {
|
||||
* @param sender station for which the DX Cluster spot is generated
|
||||
* @return locator with up to two optional AP entries
|
||||
*/
|
||||
private String buildDxClusterSpotComment(ChatMember sender) {
|
||||
static String buildDxClusterSpotComment(ChatMember sender) {
|
||||
if (sender == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String locator = sender.getQra() == null
|
||||
? ""
|
||||
: sender.getQra().trim();
|
||||
: sender.getQra().trim().toUpperCase(Locale.ROOT);
|
||||
|
||||
AirPlaneReflectionInfo reflectionInfo =
|
||||
sender.getAirPlaneReflectInfo();
|
||||
@@ -1744,7 +1882,7 @@ public class MessageBusManagementThread extends Thread {
|
||||
|
||||
aircraftComments.add(
|
||||
aircraft.getArrivingDurationMinutes()
|
||||
+ "min, "
|
||||
+ "m/"
|
||||
+ aircraft.getPotential()
|
||||
+ "%"
|
||||
);
|
||||
@@ -1755,11 +1893,11 @@ public class MessageBusManagementThread extends Thread {
|
||||
}
|
||||
|
||||
String apComment =
|
||||
"AP: " + String.join("; ", aircraftComments);
|
||||
"AP " + String.join(";", aircraftComments);
|
||||
|
||||
return locator.isEmpty()
|
||||
? apComment
|
||||
: locator + " , " + apComment;
|
||||
: locator + " " + apComment;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1791,6 +1929,101 @@ public class MessageBusManagementThread extends Thread {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds and validates one automatic private reply.
|
||||
*
|
||||
* <p>A QRG request is answered only when the QRG belonging to the
|
||||
* incoming chat category is available. The generic answer is used
|
||||
* only for other private messages and only when it contains actual
|
||||
* text.</p>
|
||||
*
|
||||
* <p>The complete message is validated before it enters the transmit
|
||||
* queue. Invalid configuration values must neither produce an empty
|
||||
* automatic reply nor start the cooldown.</p>
|
||||
*
|
||||
* @param incoming incoming private message
|
||||
* @param qrgRequested whether the message contains a recognised QRG request
|
||||
* @param genericEnabled whether the general automatic reply is enabled
|
||||
* @return validated message text or {@code null} when no reply may be sent
|
||||
*/
|
||||
private String buildAutoAnswerMessageText(
|
||||
ChatMessage incoming,
|
||||
boolean qrgRequested,
|
||||
boolean genericEnabled
|
||||
) {
|
||||
if (incoming == null
|
||||
|| incoming.getSender() == null
|
||||
|| incoming.getSender().getCallSign() == null
|
||||
|| incoming.getSender().getCallSign().isBlank()) {
|
||||
|
||||
System.err.println(
|
||||
"KST4Contest auto-answer skipped: "
|
||||
+ "incoming message has no valid sender callsign."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
String payload;
|
||||
|
||||
if (qrgRequested) {
|
||||
String qrg = getAutoAnswerQrgForCategory(
|
||||
incoming.getChatCategory()
|
||||
);
|
||||
|
||||
if (qrg == null || qrg.isBlank()) {
|
||||
System.err.println(
|
||||
"KST4Contest QRG auto-answer skipped for "
|
||||
+ incoming.getSender().getCallSign()
|
||||
+ ": no QRG is available for chat category "
|
||||
+ autoAnswerCooldownKey(incoming)
|
||||
+ "."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
payload = "QRG is: " + qrg.trim();
|
||||
|
||||
} else if (genericEnabled) {
|
||||
payload = this.client.getChatPreferences()
|
||||
.getMessageHandling_autoAnswerTextMainCat();
|
||||
|
||||
if (payload == null || payload.isBlank()) {
|
||||
System.err.println(
|
||||
"KST4Contest generic auto-answer skipped for "
|
||||
+ incoming.getSender().getCallSign()
|
||||
+ ": the configured answer text is empty."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
payload = payload.trim();
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
String messageText =
|
||||
"/CQ "
|
||||
+ incoming.getSender().getCallSign().trim()
|
||||
+ " "
|
||||
+ AUTOANSWER_PREFIX
|
||||
+ " "
|
||||
+ payload;
|
||||
|
||||
try {
|
||||
return On4KstProtocol.messageText(messageText);
|
||||
|
||||
} catch (IllegalArgumentException invalidMessage) {
|
||||
System.err.println(
|
||||
"KST4Contest auto-answer skipped for "
|
||||
+ incoming.getSender().getCallSign()
|
||||
+ ": "
|
||||
+ invalidMessage.getMessage()
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a message carries the fixed marker used for automatic replies.
|
||||
*/
|
||||
@@ -1905,13 +2138,17 @@ public class MessageBusManagementThread extends Thread {
|
||||
while (true) {
|
||||
|
||||
try {
|
||||
messageTextRaw = client.getMessageRXBus().take();
|
||||
messageTextRaw = receiveQueue.take();
|
||||
|
||||
if (messageTextRaw.getMessageText().equals(ApplicationConstants.DISCONNECT_RDR_POISONPILL) && messageTextRaw.getMessageSenderName().equals(ApplicationConstants.DISCONNECT_RDR_POISONPILL)) {
|
||||
client.getMessageRXBus().clear();
|
||||
if (ApplicationConstants.DISCONNECT_RDR_POISONPILL.equals(messageTextRaw.getMessageText())
|
||||
&& ApplicationConstants.DISCONNECT_RDR_POISONPILL.equals(messageTextRaw.getMessageSenderName())) {
|
||||
receiveQueue.clear();
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (!connectionSessionIsActive.test(connectionSessionId)) {
|
||||
break;
|
||||
}
|
||||
messageLine = messageTextRaw.getMessageText();
|
||||
|
||||
/***********************************************
|
||||
@@ -1946,8 +2183,11 @@ public class MessageBusManagementThread extends Thread {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
System.out.println(messageTextRaw.getMessageText() + " <- RXed"); // Stdout at
|
||||
// Console#######################################################TODO:Wichtig
|
||||
if (!On4KstProtocol.isConnectionProbeResponse(
|
||||
messageTextRaw.getMessageText())) {
|
||||
System.out.println(messageTextRaw.getMessageText() + " <- RXed"); // Stdout at
|
||||
// Console#######################################################TODO:Wichtig
|
||||
}
|
||||
|
||||
try {
|
||||
processRXMessage23001(messageTextRaw);
|
||||
@@ -1976,4 +2216,4 @@ public class MessageBusManagementThread extends Thread {
|
||||
System.out.println("Msgbusmgt: interrupt");
|
||||
this.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,964 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jdk.net.ExtendedSocketOptions;
|
||||
import kst4contest.ApplicationConstants;
|
||||
import kst4contest.model.ChatCategory;
|
||||
import kst4contest.model.ChatMember;
|
||||
import kst4contest.model.ChatMessage;
|
||||
import kst4contest.model.ChatPreferences;
|
||||
|
||||
/**
|
||||
* Owns the complete lifecycle of the single ON4KST TCP session.
|
||||
*
|
||||
* <p>Every reader, writer, queue and parser belongs to an immutable session id.
|
||||
* A delayed failure from an old socket can therefore never close or consume data
|
||||
* from its replacement.</p>
|
||||
*/
|
||||
final class On4KstConnectionManager {
|
||||
private static final Logger LOGGER =
|
||||
Logger.getLogger(On4KstConnectionManager.class.getName());
|
||||
private static final DateTimeFormatter LIVE_MESSAGE_TIMESTAMP =
|
||||
DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
static final int CONNECT_TIMEOUT_MILLIS = 10_000; //TCP-Connect-Timeout
|
||||
static final long LOGIN_FALLBACK_MILLIS = 2_000L; //Login-Fallback
|
||||
static final long HANDSHAKE_TIMEOUT_MILLIS = 45_000L; //Handshake-Timeout
|
||||
static final long APPLICATION_HEARTBEAT_AFTER_MILLIS = 90_000L; //Application-Heartbeat
|
||||
/** Idle duration after which the server is asked for current DX data. */
|
||||
static final long CONNECTION_PROBE_AFTER_MILLIS = 180_000L; //Active connection probe
|
||||
static final long INBOUND_STALE_AFTER_MILLIS = 210_000L; //Stale-Timeout - time without rxed data
|
||||
static final List<Long> RECONNECT_DELAYS_MILLIS =
|
||||
List.of(2_000L, 5_000L, 10_000L, 20_000L, 30_000L); //Reconnect-Backoff if no connection possible
|
||||
|
||||
private final ChatController controller;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final AtomicLong generation = new AtomicLong();
|
||||
private final AtomicLong lastReceivedMessageTimestamp = new AtomicLong();
|
||||
|
||||
private volatile Session activeSession;
|
||||
private volatile On4KstConnectionState state =
|
||||
On4KstConnectionState.DISCONNECTED;
|
||||
private volatile boolean stopRequested = true;
|
||||
private int reconnectAttempt;
|
||||
|
||||
On4KstConnectionManager(ChatController controller) {
|
||||
this.controller = controller;
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
|
||||
Thread thread = new Thread(runnable, "On4KstConnectionSupervisor");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
this.scheduler.scheduleAtFixedRate(
|
||||
this::monitorActiveSession, 5L, 5L, TimeUnit.SECONDS);
|
||||
LOGGER.fine("ON4KST connection supervisor initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last lifecycle state published by the connection supervisor.
|
||||
*
|
||||
* @return current immutable connection-state value
|
||||
*/
|
||||
On4KstConnectionState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a callback still belongs to the currently installed session.
|
||||
*
|
||||
* <p>Every reconnect receives a new id. Late EOF, write or parser callbacks from
|
||||
* an obsolete socket therefore become harmless instead of closing the replacement
|
||||
* connection.</p>
|
||||
*
|
||||
* @param sessionId id captured by the calling worker
|
||||
* @return {@code true} only for the current, open and non-stopped session
|
||||
*/
|
||||
boolean isActiveSession(long sessionId) {
|
||||
Session session = activeSession;
|
||||
return session != null
|
||||
&& session.id == sessionId
|
||||
&& !session.closed
|
||||
&& !stopRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a non-blocking connection attempt.
|
||||
*
|
||||
* <p>Configuration is validated before a socket is opened. A duplicate Connect
|
||||
* action is ignored while another attempt or session is active. Connection work
|
||||
* runs on the supervisor executor, so an unreachable server cannot block the
|
||||
* JavaFX application thread.</p>
|
||||
*/
|
||||
void start() {
|
||||
long token;
|
||||
synchronized (this) {
|
||||
if (!stopRequested && state.isConnectionAttemptActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
validateConfiguration();
|
||||
} catch (IllegalArgumentException invalidConfiguration) {
|
||||
stopRequested = true;
|
||||
transition(On4KstConnectionState.DISCONNECTED,
|
||||
"Invalid ON4KST configuration: "
|
||||
+ invalidConfiguration.getMessage(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
stopRequested = false;
|
||||
reconnectAttempt = 0;
|
||||
token = generation.incrementAndGet();
|
||||
transition(On4KstConnectionState.CONNECTING,
|
||||
"Opening ON4KST connection", false);
|
||||
}
|
||||
|
||||
scheduler.execute(() -> openConnection(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the current session and invalidates every scheduled callback or reconnect
|
||||
* belonging to it.
|
||||
*/
|
||||
void stopByUser() {
|
||||
Session oldSession;
|
||||
synchronized (this) {
|
||||
stopRequested = true;
|
||||
generation.incrementAndGet();
|
||||
transition(On4KstConnectionState.STOPPING,
|
||||
"Disconnecting from ON4KST", false);
|
||||
oldSession = activeSession;
|
||||
activeSession = null;
|
||||
}
|
||||
|
||||
closeSession(oldSession);
|
||||
controller.onOn4KstConnectionLost();
|
||||
transition(On4KstConnectionState.DISCONNECTED,
|
||||
"Disconnected by user", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records one received protocol line as proof of application-level liveness.
|
||||
*
|
||||
* <p>TCP's {@code isConnected()} only states that a connection once succeeded.
|
||||
* It does not prove that the peer is still reachable. Updating the inbound
|
||||
* timestamp here gives the monitor a meaningful end-to-end signal.</p>
|
||||
*
|
||||
* @param sessionId immutable source-session id
|
||||
* @param line complete protocol line received from ON4KST
|
||||
*/
|
||||
void onInboundActivity(long sessionId, String line) {
|
||||
Session session = activeSession;
|
||||
if (session == null || session.id != sessionId || session.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
session.lastInboundMillis.set(now);
|
||||
session.lastProgressMillis.set(now);
|
||||
|
||||
String opcode = On4KstProtocol.opcode(line);
|
||||
long probeResponseMillis = session.connectionProbe.acknowledge(now);
|
||||
if (probeResponseMillis >= 0L) {
|
||||
LOGGER.log(Level.INFO,
|
||||
"ON4KST connection probe confirmed: session {0}, "
|
||||
+ "received opcode {1}, response time {2} ms",
|
||||
new Object[] {
|
||||
sessionId,
|
||||
opcode,
|
||||
probeResponseMillis
|
||||
});
|
||||
}
|
||||
|
||||
if ("CK".equals(opcode)) {
|
||||
sendHeartbeat(session);
|
||||
}
|
||||
|
||||
if (!session.loginSent
|
||||
&& line != null
|
||||
&& line.toLowerCase(Locale.ROOT).contains("login")) {
|
||||
scheduler.execute(() -> sendLogin(sessionId));
|
||||
}
|
||||
|
||||
if ("CH".equals(opcode) || "CR".equals(opcode)) {
|
||||
recordHistoryTimestamp(line);
|
||||
}
|
||||
}
|
||||
|
||||
void onLogstat(long sessionId, String[] fields) {
|
||||
String[] copy = fields == null ? new String[0] : fields.clone();
|
||||
scheduler.execute(() -> handleLogstat(sessionId, copy));
|
||||
}
|
||||
|
||||
void stageInitialChatMember(long sessionId, ChatMember member) {
|
||||
Session session = activeSession;
|
||||
if (session == null || session.id != sessionId || member == null
|
||||
|| member.getChatCategory() == null || member.getCallSign() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int category = member.getChatCategory().getCategoryNumber();
|
||||
session.initialMembers
|
||||
.computeIfAbsent(category, ignored -> new ConcurrentHashMap<>())
|
||||
.put(member.getCallSign().trim().toUpperCase(Locale.ROOT), member);
|
||||
session.lastProgressMillis.set(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
void onInitialUserListCompleted(long sessionId, ChatCategory category) {
|
||||
if (category == null) {
|
||||
return;
|
||||
}
|
||||
scheduler.execute(() -> completeInitialUserList(
|
||||
sessionId, category.getCategoryNumber()));
|
||||
}
|
||||
|
||||
private void openConnection(long token) {
|
||||
if (!mayOpen(token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.log(Level.INFO,
|
||||
"Opening ON4KST TCP session {0}", token);
|
||||
Socket socket = new Socket();
|
||||
try {
|
||||
ChatPreferences preferences = controller.getChatPreferences();
|
||||
socket.connect(new InetSocketAddress(
|
||||
preferences.getStn_on4kstServersDns(),
|
||||
preferences.getStn_on4kstServersPort()),
|
||||
CONNECT_TIMEOUT_MILLIS);
|
||||
configureSocket(socket);
|
||||
LOGGER.log(Level.INFO,
|
||||
"ON4KST TCP session {0} connected to {1}",
|
||||
new Object[] {token, socket.getRemoteSocketAddress()});
|
||||
|
||||
LinkedBlockingQueue<ChatMessage> receiveQueue =
|
||||
new LinkedBlockingQueue<>();
|
||||
LinkedBlockingQueue<ChatMessage> transmitQueue =
|
||||
new LinkedBlockingQueue<>();
|
||||
int mainCategory = controller.getChatPreferences()
|
||||
.getLoginChatCategoryMain()
|
||||
.getCategoryNumber();
|
||||
Session session = new Session(
|
||||
token,
|
||||
socket,
|
||||
receiveQueue,
|
||||
transmitQueue,
|
||||
mainCategory);
|
||||
|
||||
ReadThread readThread = new ReadThread(
|
||||
token, socket, receiveQueue, this::isActiveSession,
|
||||
line -> onInboundActivity(token, line),
|
||||
failure -> onConnectionFailure(token, failure));
|
||||
WriteThread writeThread = new WriteThread(
|
||||
token, socket, transmitQueue,
|
||||
mainCategory,
|
||||
this::isActiveSession,
|
||||
failure -> onConnectionFailure(token, failure),
|
||||
controller::onOn4KstOutboundFrameRejected);
|
||||
MessageBusManagementThread messageProcessor =
|
||||
new MessageBusManagementThread(
|
||||
controller, controller, token, receiveQueue,
|
||||
this::isActiveSession);
|
||||
|
||||
session.readThread = readThread;
|
||||
session.writeThread = writeThread;
|
||||
session.messageProcessor = messageProcessor;
|
||||
|
||||
synchronized (this) {
|
||||
if (!mayOpen(token)) {
|
||||
closeSession(session);
|
||||
return;
|
||||
}
|
||||
activeSession = session;
|
||||
controller.installOn4KstSession(
|
||||
token, socket, receiveQueue, transmitQueue,
|
||||
readThread, writeThread, messageProcessor);
|
||||
transition(On4KstConnectionState.WAITING_FOR_LOGIN_PROMPT,
|
||||
"TCP connected; waiting for ON4KST login prompt", false);
|
||||
}
|
||||
|
||||
messageProcessor.start();
|
||||
writeThread.start();
|
||||
readThread.start();
|
||||
scheduler.schedule(
|
||||
() -> sendLogin(token), LOGIN_FALLBACK_MILLIS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
} catch (Throwable exception) {
|
||||
// Errors must be caught as well: an Error escaping here would be
|
||||
// swallowed by the scheduler and leave the state machine stuck in
|
||||
// CONNECTING without any reconnect attempt or user visible failure.
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ignored) {
|
||||
// The original connection exception is more useful.
|
||||
}
|
||||
scheduler.execute(() -> handleOpenFailure(token, exception));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mayOpen(long token) {
|
||||
return !stopRequested && generation.get() == token;
|
||||
}
|
||||
|
||||
private void sendLogin(long sessionId) {
|
||||
Session session = activeSession;
|
||||
if (session == null || session.id != sessionId || session.loginSent
|
||||
|| session.closed || stopRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ChatPreferences preferences = controller.getChatPreferences();
|
||||
int mainCategory = preferences.getLoginChatCategoryMain()
|
||||
.getCategoryNumber();
|
||||
long historyFrom = Math.max(
|
||||
0L, lastReceivedMessageTimestamp.get() - 1L);
|
||||
String login = On4KstProtocol.login(
|
||||
preferences.getStn_loginCallSign(),
|
||||
preferences.getStn_loginPassword(),
|
||||
mainCategory,
|
||||
"KST4Contest v" + ApplicationConstants.APPLICATION_CURRENT_VERSION,
|
||||
historyFrom);
|
||||
|
||||
session.loginSent = true;
|
||||
session.lastProgressMillis.set(System.currentTimeMillis());
|
||||
LOGGER.log(Level.INFO,
|
||||
"Sending ON4KST login for session {0}, main category {1}",
|
||||
new Object[] {sessionId, mainCategory});
|
||||
transition(On4KstConnectionState.AUTHENTICATING,
|
||||
"ON4KST login sent", false);
|
||||
sendControl(session, login);
|
||||
} catch (IllegalArgumentException invalidConfiguration) {
|
||||
failPermanently(session,
|
||||
"Invalid ON4KST login configuration: "
|
||||
+ invalidConfiguration.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLogstat(long sessionId, String[] fields) {
|
||||
Session session = activeSession;
|
||||
if (session == null || session.id != sessionId || session.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
String code = fields.length > 1 ? fields[1] : "";
|
||||
if (!"100".equals(code)) {
|
||||
String serverText = fields.length > 2 ? fields[2] : "Login rejected";
|
||||
failPermanently(session,
|
||||
"ON4KST login rejected (" + code + "): " + serverText);
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.authenticated) {
|
||||
return;
|
||||
}
|
||||
session.authenticated = true;
|
||||
session.lastProgressMillis.set(System.currentTimeMillis());
|
||||
LOGGER.log(Level.INFO,
|
||||
"ON4KST login accepted for session {0}", sessionId);
|
||||
|
||||
int mainCategory = controller.getChatPreferences()
|
||||
.getLoginChatCategoryMain().getCategoryNumber();
|
||||
transition(On4KstConnectionState.SYNCING_MAIN_CHAT,
|
||||
"Login accepted; loading main chat", false);
|
||||
sendControl(session, On4KstProtocol.settingsDone(mainCategory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the initial user snapshot for one chat category exactly once.
|
||||
*
|
||||
* <p>ON4KST can send further {@code UE} frames after live user updates or
|
||||
* after commands such as {@code SETNAME} and {@code BACK}. Those frames do
|
||||
* not announce a new, empty snapshot. Treating them as another initial-list
|
||||
* completion would remove the already published members because the staging
|
||||
* map was consumed by the first {@code UE} frame.</p>
|
||||
*
|
||||
* <p>The completed-category set is updated before the staging map is removed.
|
||||
* This makes the operation idempotent even if completion callbacks should
|
||||
* later be invoked from more than one thread. A genuinely empty initial list
|
||||
* remains valid: the first {@code UE} for a category is always processed,
|
||||
* even when no preceding valid {@code UA0} frame was staged.</p>
|
||||
*
|
||||
* @param sessionId immutable id of the socket session that received the frame
|
||||
* @param categoryNumber numeric ON4KST category terminated by {@code UE}
|
||||
*/
|
||||
private void completeInitialUserList(long sessionId, int categoryNumber) {
|
||||
Session session = activeSession;
|
||||
if (session == null || session.id != sessionId || session.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.completedInitialUserLists.add(categoryNumber)) {
|
||||
LOGGER.log(Level.FINE,
|
||||
"ON4KST session {0}: ignoring duplicate user-list end "
|
||||
+ "marker for category {1}; the initial snapshot "
|
||||
+ "has already been published",
|
||||
new Object[] {sessionId, categoryNumber});
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, ChatMember> staged =
|
||||
session.initialMembers.remove(categoryNumber);
|
||||
Collection<ChatMember> completeMembers = staged == null
|
||||
? List.of()
|
||||
: new ArrayList<>(staged.values());
|
||||
|
||||
LOGGER.log(Level.INFO,
|
||||
"ON4KST session {0}: complete user list for category {1} "
|
||||
+ "contains {2} valid users",
|
||||
new Object[] {
|
||||
sessionId,
|
||||
categoryNumber,
|
||||
completeMembers.size()
|
||||
});
|
||||
|
||||
controller.replaceActiveChatMembersForCategory(
|
||||
sessionId,
|
||||
new ChatCategory(categoryNumber),
|
||||
completeMembers);
|
||||
|
||||
ChatPreferences preferences = controller.getChatPreferences();
|
||||
int mainCategory =
|
||||
preferences.getLoginChatCategoryMain().getCategoryNumber();
|
||||
|
||||
if (categoryNumber == mainCategory && !session.mainListComplete) {
|
||||
session.mainListComplete = true;
|
||||
configureMainChat(session);
|
||||
|
||||
if (hasDistinctSecondChat(preferences)) {
|
||||
int secondCategory =
|
||||
preferences.getLoginChatCategorySecond()
|
||||
.getCategoryNumber();
|
||||
|
||||
transition(
|
||||
On4KstConnectionState.SYNCING_SECOND_CHAT,
|
||||
"Main chat ready; loading second chat",
|
||||
false);
|
||||
|
||||
sendControl(
|
||||
session,
|
||||
On4KstProtocol.addChat(
|
||||
secondCategory,
|
||||
Math.max(
|
||||
0L,
|
||||
lastReceivedMessageTimestamp.get() - 1L)));
|
||||
} else {
|
||||
markOnline(session);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasDistinctSecondChat(preferences)
|
||||
&& categoryNumber
|
||||
== preferences.getLoginChatCategorySecond()
|
||||
.getCategoryNumber()
|
||||
&& !session.secondListComplete) {
|
||||
session.secondListComplete = true;
|
||||
configureSecondChat(session);
|
||||
markOnline(session);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureMainChat(Session session) {
|
||||
ChatPreferences preferences = controller.getChatPreferences();
|
||||
int category = preferences.getLoginChatCategoryMain().getCategoryNumber();
|
||||
sendControl(session, On4KstProtocol.setLocator(
|
||||
category, preferences.getStn_loginLocatorMainCat()));
|
||||
if (preferences.getStn_loginNameMainCat() != null
|
||||
&& !preferences.getStn_loginNameMainCat().isBlank()) {
|
||||
sendControl(session, On4KstProtocol.setName(
|
||||
category, preferences.getStn_loginNameMainCat()));
|
||||
}
|
||||
sendControl(session, On4KstProtocol.back(category));
|
||||
|
||||
String secondLocator = preferences.getStn_loginLocatorSecondCat();
|
||||
String mainLocator = preferences.getStn_loginLocatorMainCat();
|
||||
if (preferences.isLoginToSecondChatEnabled()
|
||||
&& secondLocator != null && !secondLocator.isBlank()
|
||||
&& !secondLocator.equalsIgnoreCase(mainLocator)) {
|
||||
controller.onOn4KstConnectionWarning(
|
||||
"ON4KST uses one locator per TCP session. The second-chat locator '"
|
||||
+ secondLocator + "' is ignored; using '" + mainLocator + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSecondChat(Session session) {
|
||||
ChatPreferences preferences = controller.getChatPreferences();
|
||||
int category = preferences.getLoginChatCategorySecond().getCategoryNumber();
|
||||
if (preferences.getStn_loginNameSecondCat() != null
|
||||
&& !preferences.getStn_loginNameSecondCat().isBlank()) {
|
||||
sendControl(session, On4KstProtocol.setName(
|
||||
category, preferences.getStn_loginNameSecondCat()));
|
||||
}
|
||||
sendControl(session, On4KstProtocol.back(category));
|
||||
}
|
||||
|
||||
private void markOnline(Session session) {
|
||||
if (!isActiveSession(session.id)) {
|
||||
return;
|
||||
}
|
||||
reconnectAttempt = 0;
|
||||
session.online = true;
|
||||
session.lastProgressMillis.set(System.currentTimeMillis());
|
||||
LOGGER.log(Level.INFO,
|
||||
"ON4KST session {0} is authenticated and synchronized",
|
||||
session.id);
|
||||
transition(On4KstConnectionState.ONLINE,
|
||||
"ON4KST session is authenticated and synchronized", false);
|
||||
controller.onOn4KstConnectionOnline();
|
||||
}
|
||||
|
||||
private void sendControl(Session session, String frame) {
|
||||
if (session == null || !isActiveSession(session.id)) {
|
||||
return;
|
||||
}
|
||||
ChatMessage message = new ChatMessage();
|
||||
message.setMessageDirectedToServer(true);
|
||||
message.setMessageText(frame);
|
||||
session.transmitQueue.offer(message);
|
||||
}
|
||||
|
||||
private void sendHeartbeat(Session session) {
|
||||
if (session == null || !isActiveSession(session.id)) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
session.lastHeartbeatMillis.set(now);
|
||||
LOGGER.log(Level.FINE,
|
||||
"Sending application heartbeat for ON4KST session {0}",
|
||||
session.id);
|
||||
ChatMessage heartbeat = new ChatMessage();
|
||||
heartbeat.setMessageDirectedToServer(true);
|
||||
heartbeat.setMessageText("");
|
||||
session.transmitQueue.offer(heartbeat);
|
||||
}
|
||||
|
||||
private void sendConnectionProbe(
|
||||
Session session,
|
||||
long now,
|
||||
long inboundIdle
|
||||
) {
|
||||
if (session == null || !isActiveSession(session.id)
|
||||
|| !session.connectionProbe.tryStart(now)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.log(Level.INFO,
|
||||
"Sending ON4KST connection probe: session {0}, main category "
|
||||
+ "{1}, inbound idle {2} seconds",
|
||||
new Object[] {
|
||||
session.id,
|
||||
session.mainCategory,
|
||||
inboundIdle / 1_000L
|
||||
});
|
||||
sendControl(
|
||||
session,
|
||||
On4KstProtocol.connectionProbe(session.mainCategory));
|
||||
}
|
||||
|
||||
private void onConnectionFailure(long sessionId, Throwable failure) {
|
||||
scheduler.execute(() -> failSession(sessionId, failure));
|
||||
}
|
||||
|
||||
private void failSession(long sessionId, Throwable failure) {
|
||||
Session failedSession;
|
||||
synchronized (this) {
|
||||
failedSession = activeSession;
|
||||
if (failedSession == null || failedSession.id != sessionId
|
||||
|| failedSession.closed) {
|
||||
return;
|
||||
}
|
||||
activeSession = null;
|
||||
failedSession.closed = true;
|
||||
}
|
||||
|
||||
closeSession(failedSession);
|
||||
controller.onOn4KstConnectionLost();
|
||||
if (stopRequested) {
|
||||
transition(On4KstConnectionState.DISCONNECTED,
|
||||
"ON4KST connection stopped", false);
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleReconnect(failure);
|
||||
}
|
||||
|
||||
private void handleOpenFailure(long token, Throwable failure) {
|
||||
if (!mayOpen(token)) {
|
||||
return;
|
||||
}
|
||||
controller.onOn4KstConnectionLost();
|
||||
scheduleReconnect(failure);
|
||||
}
|
||||
|
||||
private void scheduleReconnect(Throwable failure) {
|
||||
if (stopRequested) {
|
||||
transition(On4KstConnectionState.DISCONNECTED,
|
||||
"ON4KST connection stopped", false);
|
||||
return;
|
||||
}
|
||||
|
||||
String reason = describeFailure(failure);
|
||||
LOGGER.log(Level.WARNING,
|
||||
"ON4KST connection lost; automatic reconnect scheduled", failure);
|
||||
long delay = RECONNECT_DELAYS_MILLIS.get(Math.min(
|
||||
reconnectAttempt, RECONNECT_DELAYS_MILLIS.size() - 1));
|
||||
reconnectAttempt++;
|
||||
transition(On4KstConnectionState.RECONNECT_WAIT,
|
||||
"Connection lost (" + reason + "); reconnecting in "
|
||||
+ Duration.ofMillis(delay).toSeconds() + " s", true);
|
||||
|
||||
long nextToken = generation.incrementAndGet();
|
||||
scheduler.schedule(() -> {
|
||||
if (!mayOpen(nextToken)) {
|
||||
return;
|
||||
}
|
||||
transition(On4KstConnectionState.CONNECTING,
|
||||
"Reconnecting to ON4KST", false);
|
||||
openConnection(nextToken);
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void failPermanently(Session session, String reason) {
|
||||
if (session == null || !isActiveSession(session.id)) {
|
||||
return;
|
||||
}
|
||||
stopRequested = true;
|
||||
generation.incrementAndGet();
|
||||
activeSession = null;
|
||||
session.closed = true;
|
||||
closeSession(session);
|
||||
controller.onOn4KstConnectionLost();
|
||||
LOGGER.log(Level.WARNING, reason);
|
||||
transition(On4KstConnectionState.DISCONNECTED, reason, true);
|
||||
}
|
||||
|
||||
private void monitorActiveSession() {
|
||||
try {
|
||||
Session session = activeSession;
|
||||
if (session == null || session.closed || stopRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.socket.isClosed()) {
|
||||
failSession(session.id,
|
||||
new SocketException("Socket is closed"));
|
||||
return;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (!session.online
|
||||
&& now - session.lastProgressMillis.get()
|
||||
> HANDSHAKE_TIMEOUT_MILLIS) {
|
||||
failSession(session.id,
|
||||
new SocketException("ON4KST handshake timed out"));
|
||||
return;
|
||||
}
|
||||
|
||||
long lastInboundMillis = session.lastInboundMillis.get();
|
||||
long inboundIdle = now - lastInboundMillis;
|
||||
IdleAction idleAction = determineIdleAction(
|
||||
inboundIdle,
|
||||
session.lastHeartbeatMillis.get() >= lastInboundMillis,
|
||||
session.connectionProbe.isOutstanding());
|
||||
|
||||
if (session.lastInboundMillis.get() != lastInboundMillis) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (idleAction) {
|
||||
case TIMEOUT -> {
|
||||
if (session.lastInboundMillis.get() != lastInboundMillis) {
|
||||
return;
|
||||
}
|
||||
long probeWaitMillis =
|
||||
session.connectionProbe.responseWaitMillis(now);
|
||||
if (probeWaitMillis >= 0L) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
"ON4KST connection probe timed out: session "
|
||||
+ "{0}, main category {1}, no response "
|
||||
+ "for {2} ms, inbound idle {3} seconds; "
|
||||
+ "reconnecting",
|
||||
new Object[] {
|
||||
session.id,
|
||||
session.mainCategory,
|
||||
probeWaitMillis,
|
||||
inboundIdle / 1_000L
|
||||
});
|
||||
}
|
||||
failSession(session.id,
|
||||
new SocketException("No ON4KST data received for "
|
||||
+ inboundIdle / 1_000L + " seconds"));
|
||||
}
|
||||
case CONNECTION_PROBE ->
|
||||
sendConnectionProbe(session, now, inboundIdle);
|
||||
case HEARTBEAT -> sendHeartbeat(session);
|
||||
case NONE -> {
|
||||
// The session is active or already has the required idle action.
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException exception) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
"ON4KST connection monitor failed", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects at most one maintenance action for the current inbound idle phase.
|
||||
*/
|
||||
static IdleAction determineIdleAction(
|
||||
long inboundIdleMillis,
|
||||
boolean heartbeatSentForIdlePhase,
|
||||
boolean probeOutstanding
|
||||
) {
|
||||
if (inboundIdleMillis > INBOUND_STALE_AFTER_MILLIS) {
|
||||
return IdleAction.TIMEOUT;
|
||||
}
|
||||
if (inboundIdleMillis >= CONNECTION_PROBE_AFTER_MILLIS
|
||||
&& !probeOutstanding) {
|
||||
return IdleAction.CONNECTION_PROBE;
|
||||
}
|
||||
if (inboundIdleMillis > APPLICATION_HEARTBEAT_AFTER_MILLIS
|
||||
&& !heartbeatSentForIdlePhase) {
|
||||
return IdleAction.HEARTBEAT;
|
||||
}
|
||||
return IdleAction.NONE;
|
||||
}
|
||||
|
||||
private void validateConfiguration() {
|
||||
ChatPreferences preferences = controller.getChatPreferences();
|
||||
On4KstProtocol.login(
|
||||
preferences.getStn_loginCallSign(),
|
||||
preferences.getStn_loginPassword(),
|
||||
preferences.getLoginChatCategoryMain().getCategoryNumber(),
|
||||
"KST4Contest v" + ApplicationConstants.APPLICATION_CURRENT_VERSION,
|
||||
0L);
|
||||
On4KstProtocol.locator(preferences.getStn_loginLocatorMainCat());
|
||||
if (preferences.getStn_loginNameMainCat() != null
|
||||
&& !preferences.getStn_loginNameMainCat().isBlank()) {
|
||||
On4KstProtocol.field(
|
||||
preferences.getStn_loginNameMainCat(), "main chat name");
|
||||
}
|
||||
|
||||
if (preferences.isLoginToSecondChatEnabled()) {
|
||||
if (preferences.getLoginChatCategorySecond() == null) {
|
||||
throw new IllegalArgumentException("Second chat has no category");
|
||||
}
|
||||
On4KstProtocol.category(
|
||||
preferences.getLoginChatCategorySecond().getCategoryNumber());
|
||||
if (preferences.getLoginChatCategorySecond().getCategoryNumber()
|
||||
== preferences.getLoginChatCategoryMain().getCategoryNumber()) {
|
||||
controller.onOn4KstConnectionWarning(
|
||||
"Second ON4KST chat equals the main chat and will not be added twice.");
|
||||
}
|
||||
if (preferences.getStn_loginNameSecondCat() != null
|
||||
&& !preferences.getStn_loginNameSecondCat().isBlank()) {
|
||||
On4KstProtocol.field(
|
||||
preferences.getStn_loginNameSecondCat(), "second chat name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasDistinctSecondChat(ChatPreferences preferences) {
|
||||
return preferences.isLoginToSecondChatEnabled()
|
||||
&& preferences.getLoginChatCategorySecond() != null
|
||||
&& preferences.getLoginChatCategorySecond().getCategoryNumber()
|
||||
!= preferences.getLoginChatCategoryMain().getCategoryNumber();
|
||||
}
|
||||
|
||||
private void configureSocket(Socket socket) throws IOException {
|
||||
socket.setTcpNoDelay(true);
|
||||
socket.setKeepAlive(true);
|
||||
|
||||
try {
|
||||
socket.setOption(ExtendedSocketOptions.TCP_KEEPIDLE, 45);
|
||||
socket.setOption(ExtendedSocketOptions.TCP_KEEPINTERVAL, 15);
|
||||
socket.setOption(ExtendedSocketOptions.TCP_KEEPCOUNT, 3);
|
||||
} catch (UnsupportedOperationException | IOException | LinkageError exception) {
|
||||
// LinkageError covers runtime images built without the jdk.net module;
|
||||
// the connection stays usable, only kernel side keepalive is missing.
|
||||
LOGGER.log(Level.INFO,
|
||||
"Platform does not support configurable TCP keepalive; "
|
||||
+ "application heartbeat remains active", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSession(Session session) {
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
LOGGER.log(Level.FINE,
|
||||
"Closing ON4KST session {0}", session.id);
|
||||
session.closed = true;
|
||||
if (session.readThread != null) {
|
||||
session.readThread.interrupt();
|
||||
}
|
||||
if (session.writeThread != null) {
|
||||
session.writeThread.interrupt();
|
||||
}
|
||||
if (session.messageProcessor != null) {
|
||||
session.messageProcessor.interrupt();
|
||||
}
|
||||
try {
|
||||
session.socket.close();
|
||||
} catch (IOException exception) {
|
||||
LOGGER.log(Level.FINE, "Error closing obsolete ON4KST socket", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void transition(
|
||||
On4KstConnectionState newState,
|
||||
String detail,
|
||||
boolean critical
|
||||
) {
|
||||
On4KstConnectionState previousState = state;
|
||||
state = newState;
|
||||
Level level = critical
|
||||
|| newState == On4KstConnectionState.DISCONNECTED
|
||||
|| newState == On4KstConnectionState.RECONNECT_WAIT
|
||||
? Level.WARNING : Level.INFO;
|
||||
LOGGER.log(level,
|
||||
"ON4KST state {0} -> {1}; detail: {2}",
|
||||
new Object[] {previousState, newState, detail});
|
||||
controller.updateOn4KstConnectionState(newState, detail, critical);
|
||||
}
|
||||
|
||||
private void recordHistoryTimestamp(String line) {
|
||||
long timestamp = parseMessageTimestamp(line);
|
||||
if (timestamp > 0L) {
|
||||
lastReceivedMessageTimestamp.accumulateAndGet(timestamp, Math::max);
|
||||
}
|
||||
}
|
||||
|
||||
static long parseMessageTimestamp(String line) {
|
||||
String[] fields = line == null ? new String[0] : line.split("\\|", -1);
|
||||
if (fields.length < 3) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
try {
|
||||
long numeric = Long.parseLong(fields[2]);
|
||||
long now = System.currentTimeMillis() / 1_000L;
|
||||
if (numeric > 0L && numeric <= now + 86_400L) {
|
||||
return numeric;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
try {
|
||||
return LocalDateTime.parse(fields[2], LIVE_MESSAGE_TIMESTAMP)
|
||||
.toEpochSecond(ZoneOffset.UTC);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private String describeFailure(Throwable failure) {
|
||||
if (failure == null) {
|
||||
return "unknown error";
|
||||
}
|
||||
String message = failure.getMessage();
|
||||
return message == null || message.isBlank()
|
||||
? failure.getClass().getSimpleName() : message;
|
||||
}
|
||||
|
||||
private static final class Session {
|
||||
private final Set<Integer> completedInitialUserLists =
|
||||
ConcurrentHashMap.newKeySet();
|
||||
|
||||
private final long id;
|
||||
private final Socket socket;
|
||||
private final LinkedBlockingQueue<ChatMessage> receiveQueue;
|
||||
private final LinkedBlockingQueue<ChatMessage> transmitQueue;
|
||||
private final int mainCategory;
|
||||
private final long connectedMillis = System.currentTimeMillis();
|
||||
private final AtomicLong lastInboundMillis =
|
||||
new AtomicLong(connectedMillis);
|
||||
private final AtomicLong lastProgressMillis =
|
||||
new AtomicLong(connectedMillis);
|
||||
private final AtomicLong lastHeartbeatMillis = new AtomicLong();
|
||||
private final ConnectionProbeState connectionProbe =
|
||||
new ConnectionProbeState();
|
||||
private final Map<Integer, Map<String, ChatMember>> initialMembers =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private volatile ReadThread readThread;
|
||||
private volatile WriteThread writeThread;
|
||||
private volatile MessageBusManagementThread messageProcessor;
|
||||
private volatile boolean loginSent;
|
||||
private volatile boolean authenticated;
|
||||
private volatile boolean mainListComplete;
|
||||
private volatile boolean secondListComplete;
|
||||
private volatile boolean online;
|
||||
private volatile boolean closed;
|
||||
|
||||
private Session(
|
||||
long id,
|
||||
Socket socket,
|
||||
LinkedBlockingQueue<ChatMessage> receiveQueue,
|
||||
LinkedBlockingQueue<ChatMessage> transmitQueue,
|
||||
int mainCategory
|
||||
) {
|
||||
this.id = id;
|
||||
this.socket = socket;
|
||||
this.receiveQueue = receiveQueue;
|
||||
this.transmitQueue = transmitQueue;
|
||||
this.mainCategory = mainCategory;
|
||||
}
|
||||
}
|
||||
|
||||
/** Maintenance action selected by the session monitor. */
|
||||
enum IdleAction {
|
||||
NONE,
|
||||
HEARTBEAT,
|
||||
CONNECTION_PROBE,
|
||||
TIMEOUT
|
||||
}
|
||||
|
||||
/** Tracks one outstanding liveness probe for the complete TCP session. */
|
||||
static final class ConnectionProbeState {
|
||||
private final AtomicLong sentMillis = new AtomicLong();
|
||||
|
||||
boolean tryStart(long now) {
|
||||
return now > 0L && sentMillis.compareAndSet(0L, now);
|
||||
}
|
||||
|
||||
long acknowledge(long now) {
|
||||
long sent = sentMillis.getAndSet(0L);
|
||||
return sent == 0L ? -1L : Math.max(0L, now - sent);
|
||||
}
|
||||
|
||||
boolean isOutstanding() {
|
||||
return sentMillis.get() > 0L;
|
||||
}
|
||||
|
||||
long responseWaitMillis(long now) {
|
||||
long sent = sentMillis.get();
|
||||
return sent == 0L ? -1L : Math.max(0L, now - sent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
/**
|
||||
* Observable lifecycle of the ON4KST TCP session.
|
||||
*
|
||||
* <p>A connected TCP socket is deliberately not synonymous with an authenticated
|
||||
* chat session. The intermediate states make that distinction visible to the UI
|
||||
* and prevent application messages from being sent in the wrong protocol context.</p>
|
||||
*/
|
||||
public enum On4KstConnectionState {
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
WAITING_FOR_LOGIN_PROMPT,
|
||||
AUTHENTICATING,
|
||||
SYNCING_MAIN_CHAT,
|
||||
SYNCING_SECOND_CHAT,
|
||||
ONLINE,
|
||||
RECONNECT_WAIT,
|
||||
STOPPING;
|
||||
|
||||
/**
|
||||
* Returns whether the complete application-level ON4KST handshake has finished.
|
||||
*
|
||||
* @return {@code true} only after authentication and all requested user lists
|
||||
* have been synchronized
|
||||
*/
|
||||
public boolean isOnline() {
|
||||
return this == ONLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a connection attempt or usable session is currently owned by
|
||||
* the connection manager.
|
||||
*
|
||||
* <p>This is intentionally broader than {@link #isOnline()}. The UI uses it to
|
||||
* prevent a second Connect action while authentication, synchronization or a
|
||||
* scheduled reconnect is already in progress.</p>
|
||||
*
|
||||
* @return {@code true} while connecting, synchronizing, online or waiting for
|
||||
* an automatic reconnect
|
||||
*/
|
||||
public boolean isConnectionAttemptActive() {
|
||||
return switch (this) {
|
||||
case CONNECTING, WAITING_FOR_LOGIN_PROMPT, AUTHENTICATING,
|
||||
SYNCING_MAIN_CHAT, SYNCING_SECOND_CHAT, ONLINE,
|
||||
RECONNECT_WAIT -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Builds ON4KST port-23001 frames and rejects values that could break framing or
|
||||
* put the server into an invalid chat context.
|
||||
*
|
||||
* <p>All outbound protocol construction is concentrated here. User-controlled
|
||||
* values may therefore never introduce a field separator or a second line, and
|
||||
* category and locator validation happens before the frame reaches the socket.</p>
|
||||
*/
|
||||
final class On4KstProtocol {
|
||||
private static final Pattern LOCATOR_6 =
|
||||
Pattern.compile("^[A-Ra-r]{2}[0-9]{2}[A-Xa-x]{2}$");
|
||||
|
||||
private On4KstProtocol() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the initial authenticated login frame.
|
||||
*
|
||||
* @param callsign login callsign
|
||||
* @param password ON4KST password; never logged by this class
|
||||
* @param category primary chat category
|
||||
* @param clientName client identification sent to the server
|
||||
* @param lastMessageTimestamp earliest history timestamp to request
|
||||
* @return validated frame without CR/LF terminator
|
||||
*/
|
||||
static String login(
|
||||
String callsign,
|
||||
String password,
|
||||
int category,
|
||||
String clientName,
|
||||
long lastMessageTimestamp
|
||||
) {
|
||||
return "LOGINC|" + field(callsign, "callsign")
|
||||
+ "|" + password(password)
|
||||
+ "|" + category(category)
|
||||
+ "|" + field(clientName, "client name")
|
||||
+ "|25|0|1|" + Math.max(0L, lastMessageTimestamp) + "|0|";
|
||||
}
|
||||
|
||||
/** Builds the settings-complete frame for the supplied chat category. */
|
||||
static String settingsDone(int category) {
|
||||
return "SDONE|" + category(category) + "|";
|
||||
}
|
||||
|
||||
/** Builds the frame used to add a distinct second chat to the same session. */
|
||||
static String addChat(int category, long lastMessageTimestamp) {
|
||||
return "ACHAT|" + category(category)
|
||||
+ "|25|10|2|" + Math.max(0L, lastMessageTimestamp)
|
||||
+ "|0|";
|
||||
}
|
||||
|
||||
/** Builds the active liveness probe for the session's main chat. */
|
||||
static String connectionProbe(int category) {
|
||||
return "RDXQ|" + category(category) + "|";
|
||||
}
|
||||
|
||||
/** Returns whether a server frame is the expected liveness-probe response. */
|
||||
static boolean isConnectionProbeResponse(String frame) {
|
||||
return "DXQ".equals(opcode(frame));
|
||||
}
|
||||
|
||||
/** Extracts and normalizes the opcode without exposing the remaining frame. */
|
||||
static String opcode(String frame) {
|
||||
if (frame == null) {
|
||||
return "";
|
||||
}
|
||||
int separator = frame.indexOf('|');
|
||||
return (separator < 0 ? frame : frame.substring(0, separator))
|
||||
.trim()
|
||||
.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
/** Builds a category-qualified locator command after validating Maidenhead syntax. */
|
||||
static String setLocator(int category, String locator) {
|
||||
return command(category, "/SETLOC " + locator(locator));
|
||||
}
|
||||
|
||||
/** Builds a category-qualified chat-name command. */
|
||||
static String setName(int category, String name) {
|
||||
return command(category, "/SETNAME " + field(name, "chat name"));
|
||||
}
|
||||
|
||||
/** Builds the command that changes the operator state back to available. */
|
||||
static String back(int category) {
|
||||
return command(category, "/BACK");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps one validated slash command in an ON4KST message frame.
|
||||
*
|
||||
* @return frame without CR/LF terminator
|
||||
*/
|
||||
static String command(int category, String command) {
|
||||
return "MSG|" + category(category) + "|0|"
|
||||
+ messageText(command) + "|0|";
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps one operator chat message in a category-qualified ON4KST frame.
|
||||
*
|
||||
* @return frame without CR/LF terminator
|
||||
*/
|
||||
static String chatMessage(int category, String text) {
|
||||
return "MSG|" + category(category) + "|0|"
|
||||
+ messageText(text) + "|0|";
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes trailing line terminators from a legacy raw frame while rejecting an
|
||||
* embedded line break that could inject a second server command.
|
||||
*
|
||||
* @param frame legacy raw frame, possibly with trailing CR/LF
|
||||
* @return exactly one normalized protocol line
|
||||
* @throws IllegalArgumentException if the value is {@code null} or contains an
|
||||
* embedded line break
|
||||
*/
|
||||
static String normalizeRawFrame(String frame) {
|
||||
if (frame == null) {
|
||||
throw new IllegalArgumentException("ON4KST frame must not be null");
|
||||
}
|
||||
|
||||
int end = frame.length();
|
||||
while (end > 0) {
|
||||
char last = frame.charAt(end - 1);
|
||||
if (last != '\r' && last != '\n') {
|
||||
break;
|
||||
}
|
||||
end--;
|
||||
}
|
||||
|
||||
String normalized = frame.substring(0, end);
|
||||
if (normalized.indexOf('\r') >= 0 || normalized.indexOf('\n') >= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"ON4KST frame contains an embedded line break");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes a six-character Maidenhead locator.
|
||||
*
|
||||
* @return upper-case locator
|
||||
*/
|
||||
static String locator(String locator) {
|
||||
String normalized = field(locator, "locator").toUpperCase(Locale.ROOT);
|
||||
if (!LOCATOR_6.matcher(normalized).matches()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Locator must be a six-character Maidenhead locator: " + normalized);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/** Rejects message text containing an ON4KST field or line delimiter. */
|
||||
static String messageText(String text) {
|
||||
String value = field(text, "message text");
|
||||
if (value.indexOf('|') >= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Message text contains the ON4KST field separator '|'");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates one required, non-password protocol field.
|
||||
*
|
||||
* @param value field value
|
||||
* @param label diagnostic label used in validation errors
|
||||
* @return trimmed value
|
||||
*/
|
||||
static String field(String value, String label) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException(label + " must not be empty");
|
||||
}
|
||||
if (value.indexOf('|') >= 0
|
||||
|| value.indexOf('\r') >= 0
|
||||
|| value.indexOf('\n') >= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
label + " contains an ON4KST frame delimiter");
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private static String password(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
throw new IllegalArgumentException("password must not be empty");
|
||||
}
|
||||
if (value.indexOf('|') >= 0
|
||||
|| value.indexOf('\r') >= 0
|
||||
|| value.indexOf('\n') >= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"password contains an ON4KST frame delimiter");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the category range supported by ON4KST.
|
||||
*
|
||||
* @return the unchanged category for convenient inline use
|
||||
*/
|
||||
static int category(int category) {
|
||||
if (category < 1 || category > 12) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported ON4KST chat category: " + category);
|
||||
}
|
||||
return category;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import kst4contest.model.ChatPreferences;
|
||||
|
||||
|
||||
class On4KstProtocolTest {
|
||||
@Test
|
||||
void buildsLoginWithReplayOverlap() {
|
||||
assertEquals(
|
||||
"LOGINC|DL1ABC|secret|2|KST4Contest v1.2.3|25|0|1|12344|0|",
|
||||
On4KstProtocol.login(
|
||||
"DL1ABC", "secret", 2,
|
||||
"KST4Contest v1.2.3", 12_344L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildsContextSafeSecondChatFrames() {
|
||||
assertEquals("SDONE|2|", On4KstProtocol.settingsDone(2));
|
||||
assertEquals("ACHAT|3|25|10|2|100|0|",
|
||||
On4KstProtocol.addChat(3, 100L));
|
||||
assertEquals("MSG|2|0|/SETLOC JO31AA|0|",
|
||||
On4KstProtocol.setLocator(2, "jo31aa"));
|
||||
assertEquals("MSG|3|0|/SETNAME 10G 10368.200|0|",
|
||||
On4KstProtocol.setName(3, "10G 10368.200"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stripsOnlyTrailingLineEndings() {
|
||||
assertEquals("CK|", On4KstProtocol.normalizeRawFrame("CK|\r\n"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> On4KstProtocol.normalizeRawFrame("CK|\rBROKEN"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsValuesThatCouldCreateASecondProtocolFrame() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> On4KstProtocol.chatMessage(2, "hello|0|"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> On4KstProtocol.chatMessage(2, "hello\r\nQUIT|"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> On4KstProtocol.login(
|
||||
"DL1ABC", "bad|password", 2, "client", 0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidLocatorAndCategoryBeforeTheyReachTheServer() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> On4KstProtocol.setLocator(2, "JO31"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> On4KstProtocol.settingsDone(99));
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertsBothHistoryAndLiveMessageTimestampsForReconnect() {
|
||||
assertEquals(1_186_819_108L,
|
||||
On4KstConnectionManager.parseMessageTimestamp(
|
||||
"CR|2|1186819108|EA6VQ|Gabriel|0|msg|0|"));
|
||||
assertEquals(
|
||||
LocalDateTime.of(2026, 8, 13, 12, 34, 56)
|
||||
.toEpochSecond(ZoneOffset.UTC),
|
||||
On4KstConnectionManager.parseMessageTimestamp(
|
||||
"CH|2|20260813123456|DL1ABC|Op|0|msg|0|"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesBeaconVariablesBeforeApplyingProtocolValidation() {
|
||||
ChatPreferences preferences = new ChatPreferences();
|
||||
preferences.setMYQRGFirstCat("144.300");
|
||||
|
||||
ChatController controller = new ChatController();
|
||||
controller.setChatPreferences(preferences);
|
||||
|
||||
controller.validateBeaconTemplate(
|
||||
"calling cq at MYQRG"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
"calling cq at 144.300",
|
||||
controller.resolveAndValidateBeaconText(
|
||||
"calling cq at MYQRG"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsTemporarilyUnresolvedVariableOnlyBeaconTemplate() {
|
||||
ChatPreferences preferences = new ChatPreferences();
|
||||
preferences.setMYQRGFirstCat("");
|
||||
|
||||
ChatController controller = new ChatController();
|
||||
controller.setChatPreferences(preferences);
|
||||
|
||||
assertDoesNotThrow(
|
||||
() -> controller.validateBeaconTemplate("MYQRG")
|
||||
);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> controller.resolveAndValidateBeaconText("MYQRG")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsEmptyOverlongAndProtocolBreakingBeaconText() {
|
||||
ChatPreferences preferences = new ChatPreferences();
|
||||
|
||||
ChatController controller = new ChatController();
|
||||
controller.setChatPreferences(preferences);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> controller.validateBeaconTemplate(" ")
|
||||
);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> controller.validateBeaconTemplate(
|
||||
"cq at 144.300|0|QUIT"
|
||||
)
|
||||
);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> controller.validateBeaconTemplate(
|
||||
"x".repeat(
|
||||
ChatController.MAX_BEACON_TEXT_LENGTH
|
||||
+ 1
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import kst4contest.model.ChatMessage;
|
||||
|
||||
class On4KstSocketThreadTest {
|
||||
@Test
|
||||
@Timeout(5)
|
||||
void eofIsReportedImmediately() throws Exception {
|
||||
try (ServerSocket server = new ServerSocket(0)) {
|
||||
CompletableFuture<Void> serverDone = CompletableFuture.runAsync(() -> {
|
||||
try (Socket accepted = server.accept();
|
||||
OutputStreamWriter out = new OutputStreamWriter(
|
||||
accepted.getOutputStream(), StandardCharsets.UTF_8)) {
|
||||
out.write("CK|\r\n");
|
||||
out.flush();
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
});
|
||||
|
||||
try (Socket client = new Socket("127.0.0.1", server.getLocalPort())) {
|
||||
LinkedBlockingQueue<ChatMessage> queue = new LinkedBlockingQueue<>();
|
||||
AtomicBoolean active = new AtomicBoolean(true);
|
||||
CompletableFuture<Throwable> failure = new CompletableFuture<>();
|
||||
ReadThread reader = new ReadThread(
|
||||
7L, client, queue, ignored -> active.get(), ignored -> { },
|
||||
failure::complete);
|
||||
reader.start();
|
||||
|
||||
assertEquals("CK|", queue.poll(2, TimeUnit.SECONDS).getMessageText());
|
||||
failure.get(2, TimeUnit.SECONDS);
|
||||
active.set(false);
|
||||
reader.join(Duration.ofSeconds(2).toMillis());
|
||||
}
|
||||
serverDone.get(2, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(5)
|
||||
void writerUsesOneExactCrLfPerFrameIncludingHeartbeat() throws Exception {
|
||||
try (ServerSocket server = new ServerSocket(0)) {
|
||||
CompletableFuture<String> firstLine = new CompletableFuture<>();
|
||||
CompletableFuture<String> secondLine = new CompletableFuture<>();
|
||||
CompletableFuture<Void> serverDone = CompletableFuture.runAsync(() -> {
|
||||
try (Socket accepted = server.accept();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
accepted.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
firstLine.complete(in.readLine());
|
||||
secondLine.complete(in.readLine());
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
});
|
||||
|
||||
try (Socket client = new Socket("127.0.0.1", server.getLocalPort())) {
|
||||
LinkedBlockingQueue<ChatMessage> queue = new LinkedBlockingQueue<>();
|
||||
AtomicBoolean active = new AtomicBoolean(true);
|
||||
WriteThread writer = new WriteThread(
|
||||
11L, client, queue, 2, ignored -> active.get(),
|
||||
ignored -> { }, ignored -> { });
|
||||
writer.start();
|
||||
|
||||
queue.add(serverFrame(""));
|
||||
queue.add(serverFrame("SDONE|2|\r"));
|
||||
assertEquals("", firstLine.get(2, TimeUnit.SECONDS));
|
||||
assertEquals("SDONE|2|", secondLine.get(2, TimeUnit.SECONDS));
|
||||
|
||||
active.set(false);
|
||||
writer.interrupt();
|
||||
writer.join(Duration.ofSeconds(2).toMillis());
|
||||
}
|
||||
serverDone.get(2, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private ChatMessage serverFrame(String text) {
|
||||
ChatMessage message = new ChatMessage();
|
||||
message.setMessageDirectedToServer(true);
|
||||
message.setMessageText(text);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +1,130 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.LongPredicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import kst4contest.model.ChatMessage;
|
||||
|
||||
|
||||
/**
|
||||
* This thread is responsible for reading telnet servers input at port 23001 and printing it
|
||||
* to the console.
|
||||
* It runs in an infinite loop until the client disconnects from the server.
|
||||
* Reads exactly one immutable ON4KST connection session.
|
||||
*
|
||||
* @author www.codejava.net
|
||||
* <p>EOF is a connection-loss event, not an empty chat message. Every line is
|
||||
* associated with the session id captured by this reader, so a delayed exception
|
||||
* from an obsolete socket cannot affect a newer reconnect.</p>
|
||||
*/
|
||||
public class ReadThread extends Thread {
|
||||
private static final Logger LOGGER = Logger.getLogger(ReadThread.class.getName());
|
||||
private BufferedReader reader;
|
||||
private Socket socket;
|
||||
private ChatController client;
|
||||
public boolean accidentalDisconnected;
|
||||
|
||||
|
||||
|
||||
public boolean isAccidentalDisconnected() {
|
||||
return accidentalDisconnected;
|
||||
}
|
||||
|
||||
public void setAccidentalDisconnected(boolean accidentalDisconnected) {
|
||||
this.accidentalDisconnected = accidentalDisconnected;
|
||||
}
|
||||
private final long sessionId;
|
||||
private final Socket socket;
|
||||
private final LinkedBlockingQueue<ChatMessage> receiveQueue;
|
||||
private final LongPredicate sessionIsActive;
|
||||
private final Consumer<String> inboundActivity;
|
||||
private final Consumer<Throwable> connectionFailure;
|
||||
private final BufferedReader reader;
|
||||
|
||||
// private boolean readingFinished = true; //kst4contest.test 4 23001
|
||||
private boolean readingFinished = true;
|
||||
|
||||
InputStream input;
|
||||
|
||||
public ReadThread(Socket socket, ChatController client) {
|
||||
/**
|
||||
* Compatibility constructor for the pre-session controller path.
|
||||
*
|
||||
* @deprecated new connections should be created by
|
||||
* {@link On4KstConnectionManager}
|
||||
*/
|
||||
@Deprecated
|
||||
public ReadThread(Socket socket, ChatController client) throws IOException {
|
||||
this(0L, socket, client.getMessageRXBus(), ignored -> true,
|
||||
ignored -> { }, ignored -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the reader for one connection generation.
|
||||
*
|
||||
* @param sessionId immutable id of the owning socket session
|
||||
* @param socket connected ON4KST socket
|
||||
* @param receiveQueue private receive queue belonging to this session
|
||||
* @param sessionIsActive guard against callbacks from an obsolete session
|
||||
* @param inboundActivity callback used for liveness and protocol progress
|
||||
* @param connectionFailure callback for EOF, I/O and unexpected runtime errors
|
||||
* @throws IOException if the socket input stream cannot be opened
|
||||
*/
|
||||
public ReadThread(
|
||||
long sessionId,
|
||||
Socket socket,
|
||||
LinkedBlockingQueue<ChatMessage> receiveQueue,
|
||||
LongPredicate sessionIsActive,
|
||||
Consumer<String> inboundActivity,
|
||||
Consumer<Throwable> connectionFailure
|
||||
) throws IOException {
|
||||
this.sessionId = sessionId;
|
||||
this.socket = socket;
|
||||
this.client = client;
|
||||
|
||||
try {
|
||||
input = socket.getInputStream();
|
||||
reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error getting input stream", ex);
|
||||
}
|
||||
this.receiveQueue = receiveQueue;
|
||||
this.sessionIsActive = sessionIsActive;
|
||||
this.inboundActivity = inboundActivity;
|
||||
this.connectionFailure = connectionFailure;
|
||||
this.reader = new BufferedReader(new InputStreamReader(
|
||||
socket.getInputStream(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Thread.currentThread().setName("ReadFromTelnetThread");
|
||||
|
||||
ChatMessage message; //bugfix leak, moved out of while
|
||||
while (true) {
|
||||
|
||||
// System.out.println("rdth");
|
||||
|
||||
try {
|
||||
|
||||
String response = reader.readLine();
|
||||
message = new ChatMessage();
|
||||
message.setMessageText(response);
|
||||
|
||||
// message.setDirectedToServer(false);
|
||||
// message.setDirectedToServer(false);
|
||||
// message.setDirectedToServer(false);
|
||||
|
||||
|
||||
if (response != null) {
|
||||
client.getMessageRXBus().put(message);
|
||||
// System.out.println("[RT]: read message and added it to msgrxqueue --- " + response + " ---");
|
||||
} else {
|
||||
System.out.println("[RT]: read message responsed a nullstring, do nothing, buffersize = " + socket.getReceiveBufferSize() + ", reader ready? "
|
||||
+ reader.ready());
|
||||
// reader = new BufferedReader(new InputStreamReader(input));
|
||||
// response = reader.readLine();
|
||||
this.client.getSocket().close();
|
||||
this.interrupt();
|
||||
Thread.currentThread().setName("ReadFromOn4Kst-" + sessionId);
|
||||
LOGGER.log(Level.FINE,
|
||||
"ON4KST reader started for session {0}", sessionId);
|
||||
|
||||
try {
|
||||
while (!isInterrupted() && sessionIsActive.test(sessionId)) {
|
||||
String response = reader.readLine();
|
||||
if (response == null) {
|
||||
throw new EOFException("ON4KST closed the TCP connection");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception sexc) {
|
||||
LOGGER.log(Level.SEVERE, "[ReadThread] Socket closed unexpectedly", sexc);
|
||||
try {
|
||||
this.client.getSocket().close();
|
||||
this.interrupt();
|
||||
break;
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "[ReadThread] Error closing socket", e);
|
||||
}
|
||||
inboundActivity.accept(response);
|
||||
if (!sessionIsActive.test(sessionId)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ChatMessage message = new ChatMessage();
|
||||
message.setMessageText(response);
|
||||
receiveQueue.put(message);
|
||||
}
|
||||
|
||||
} catch (InterruptedException interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException exception) {
|
||||
if (sessionIsActive.test(sessionId)) {
|
||||
LOGGER.log(Level.FINE,
|
||||
"ON4KST read failed for session " + sessionId,
|
||||
exception);
|
||||
connectionFailure.accept(exception);
|
||||
}
|
||||
} catch (RuntimeException exception) {
|
||||
if (sessionIsActive.test(sessionId)) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected ON4KST reader failure", exception);
|
||||
connectionFailure.accept(exception);
|
||||
}
|
||||
} finally {
|
||||
LOGGER.log(Level.FINE,
|
||||
"ON4KST reader stopped for session {0}", sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interrupts the read loop and closes the session socket.
|
||||
*
|
||||
* @return always {@code true} after a successful close
|
||||
* @throws IOException if closing the reader or socket fails
|
||||
*/
|
||||
public boolean terminateConnection() throws IOException {
|
||||
this.reader.close();
|
||||
this.input.close();
|
||||
this.socket.close();
|
||||
|
||||
return true;
|
||||
interrupt();
|
||||
reader.close();
|
||||
socket.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isReadingFinished() {
|
||||
return readingFinished;
|
||||
}
|
||||
|
||||
public void setReadingFinished(boolean readingReady) {
|
||||
this.readingFinished = readingReady;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,10 +2,8 @@ package kst4contest.controller;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import kst4contest.ApplicationConstants;
|
||||
import kst4contest.model.Band;
|
||||
import kst4contest.model.ChatMember;
|
||||
import kst4contest.model.ThreadStateMessage;
|
||||
import kst4contest.view.GuiUtils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
@@ -15,7 +13,6 @@ import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -99,7 +96,7 @@ public class ReadUDPByWintestThread extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
private void processWinTestMessage(String msg) {
|
||||
void processWinTestMessage(String msg) {
|
||||
// System.out.println("Wintest-Message received: " + msg);
|
||||
|
||||
lastPacketTime = System.currentTimeMillis();
|
||||
@@ -165,18 +162,79 @@ public class ReadUDPByWintestThread extends Thread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Win-Test STATUS packets and update own QRG from WT station.
|
||||
* Formats a frequency from a Win-Test STATUS packet for use as MYQRG.
|
||||
*
|
||||
* Parsing model (tokenized with quotes preserved):
|
||||
* parts[0] = "STATUS"
|
||||
* parts[1] = station name (example: "STN1")
|
||||
* parts[5] = val2 (used to derive mode: 1 => SSB, else CW)
|
||||
* parts[7] = frequency in 0.1 kHz units (example: 1443210 => 144321.0)
|
||||
* <p>Win-Test transmits the frequency in units of 0.1 kHz. KST4Contest
|
||||
* displays frequencies as {@code MHz.kHz.10Hz}, for example
|
||||
* {@code 144.300.00} or {@code 10368.100.00}. The MHz part may contain
|
||||
* between one and five digits. Deriving its length from the complete value
|
||||
* avoids separate and incomplete handling for individual bands.</p>
|
||||
*
|
||||
* @param frequencyIn100Hz frequency received from Win-Test in units of
|
||||
* 0.1 kHz, equivalent to 100 Hz
|
||||
* @return frequency formatted for MYQRG
|
||||
* @throws IllegalArgumentException if the supplied frequency is not positive
|
||||
* or too small to be formatted
|
||||
* @throws ArithmeticException if the supplied value exceeds the supported
|
||||
* numeric range
|
||||
*/
|
||||
private String helper_formatWinTestFrequency(long frequencyIn100Hz) {
|
||||
if (frequencyIn100Hz <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Win-Test frequency must be greater than zero"
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Multiplication by ten creates a digit sequence whose final five
|
||||
* digits represent kHz and 10-Hz groups:
|
||||
*
|
||||
* 1443210 -> 14432100 -> 144.321.00
|
||||
* 103681000 -> 1036810000 -> 10368.100.00
|
||||
*/
|
||||
long frequencyIn10Hz = Math.multiplyExact(
|
||||
frequencyIn100Hz,
|
||||
10L
|
||||
);
|
||||
|
||||
String frequencyDigits = Long.toString(frequencyIn10Hz);
|
||||
|
||||
if (frequencyDigits.length() < 6) {
|
||||
throw new IllegalArgumentException(
|
||||
"Win-Test frequency is too small: " + frequencyIn100Hz
|
||||
);
|
||||
}
|
||||
|
||||
int mhzEndIndex = frequencyDigits.length() - 5;
|
||||
int khzEndIndex = frequencyDigits.length() - 2;
|
||||
|
||||
return frequencyDigits.substring(0, mhzEndIndex)
|
||||
+ "."
|
||||
+ frequencyDigits.substring(mhzEndIndex, khzEndIndex)
|
||||
+ "."
|
||||
+ frequencyDigits.substring(khzEndIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Win-Test STATUS packet and optionally updates MYQRG.
|
||||
*
|
||||
* <p>The packet is tokenised while preserving quoted station names.
|
||||
* The configured station-name filter is applied before any frequency is
|
||||
* processed. An empty filter accepts STATUS packets from every Win-Test
|
||||
* station.</p>
|
||||
*
|
||||
* <p>The main frequency is read from token 7. If pass-frequency use is
|
||||
* enabled and token 11 contains a valid frequency, the pass frequency is
|
||||
* used instead. A missing or invalid pass frequency deliberately falls
|
||||
* back to the main frequency.</p>
|
||||
*
|
||||
* @param msg complete Win-Test STATUS packet
|
||||
*/
|
||||
private void parseStatus(String msg) {
|
||||
try {
|
||||
ArrayList<String> parts = new ArrayList<>();
|
||||
Matcher matcher = STATUS_TOKEN_PATTERN.matcher(msg);
|
||||
|
||||
while (matcher.find()) {
|
||||
if (matcher.group(1) != null) {
|
||||
parts.add(matcher.group(1));
|
||||
@@ -186,86 +244,105 @@ public class ReadUDPByWintestThread extends Thread {
|
||||
}
|
||||
|
||||
if (parts.size() < 8) {
|
||||
System.out.println("[WinTest] STATUS too short: " + msg);
|
||||
System.out.println(
|
||||
"[WinTest] STATUS too short: " + msg
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
String stn = parts.get(1);
|
||||
String stationFilter = client.getChatPreferences().getLogsynch_wintestNetworkStationNameOfWintestClient1();
|
||||
if (stationFilter != null && !stationFilter.isBlank() && !stn.equalsIgnoreCase(stationFilter)) {
|
||||
String stationName = parts.get(1);
|
||||
String stationFilter = client
|
||||
.getChatPreferences()
|
||||
.getLogsynch_wintestNetworkStationNameOfWintestClient1();
|
||||
|
||||
if (stationFilter != null
|
||||
&& !stationFilter.isBlank()
|
||||
&& !stationName.equalsIgnoreCase(stationFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String val2 = parts.get(5);
|
||||
String freqRaw = parts.get(7);
|
||||
double freqFloat = Integer.parseInt(freqRaw) / 10.0;
|
||||
String modeValue = parts.get(5);
|
||||
long mainFrequencyRaw = Long.parseLong(parts.get(7));
|
||||
double mainFrequencyKHz = mainFrequencyRaw / 10.0;
|
||||
|
||||
String mode;
|
||||
if ("1".equals(val2)) {
|
||||
mode = freqFloat > 10000.0 ? "usb" : "lsb";
|
||||
|
||||
if ("1".equals(modeValue)) {
|
||||
mode = mainFrequencyKHz > 10000.0 ? "usb" : "lsb";
|
||||
} else {
|
||||
mode = "cw";
|
||||
}
|
||||
|
||||
// Format as MMM.KKK.HH display format (e.g. 144.300.00) consistent with UCX thread
|
||||
// freqFloat is in kHz (e.g. 144300.0), convert to Hz-string for formatting
|
||||
long freqHzTimes100 = Math.round(freqFloat * 100.0); // e.g. 14430000
|
||||
String hzStr = String.valueOf(freqHzTimes100);
|
||||
String formattedQRG;
|
||||
if (hzStr.length() == 8) {
|
||||
// 144MHz range: 14430000 -> 144.300.00
|
||||
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 3), hzStr.substring(3, 6), hzStr.substring(6, 8));
|
||||
} else if (hzStr.length() == 9) {
|
||||
// 1296MHz range: 129600000 -> 1296.000.00
|
||||
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 4), hzStr.substring(4, 7), hzStr.substring(7, 9));
|
||||
} else if (hzStr.length() == 7) {
|
||||
// 70MHz range: 7010000 -> 70.100.00
|
||||
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 2), hzStr.substring(2, 5), hzStr.substring(5, 7));
|
||||
} else if (hzStr.length() == 6) {
|
||||
// 50MHz range: 5030000 but 6 digits: 503000 -> 5.030.00
|
||||
formattedQRG = String.format("%s.%s.%s", hzStr.substring(0, 1), hzStr.substring(1, 4), hzStr.substring(4, 6));
|
||||
} else {
|
||||
formattedQRG = String.format(Locale.US, "%.1f", freqFloat); // fallback
|
||||
}
|
||||
// Parse pass frequency from parts[11] if available (WT STATUS format)
|
||||
String formattedPassQRG = null;
|
||||
String formattedMainQrg =
|
||||
helper_formatWinTestFrequency(mainFrequencyRaw);
|
||||
|
||||
/*
|
||||
* Token 11 may contain the pass frequency, depending on the
|
||||
* Win-Test STATUS packet. Small numeric flag values must not be
|
||||
* interpreted as frequencies.
|
||||
*/
|
||||
String formattedPassQrg = null;
|
||||
|
||||
if (parts.size() > 11) {
|
||||
try {
|
||||
String passFreqRaw = parts.get(11);
|
||||
double passFreqFloat = Integer.parseInt(passFreqRaw) / 10.0;
|
||||
if (passFreqFloat > 100) { // Must be a valid radio frequency (> 100 kHz), protects against parsing boolean flag tokens
|
||||
long passFreqHzTimes100 = Math.round(passFreqFloat * 100.0);
|
||||
String passHzStr = String.valueOf(passFreqHzTimes100);
|
||||
if (passHzStr.length() == 8) {
|
||||
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 3), passHzStr.substring(3, 6), passHzStr.substring(6, 8));
|
||||
} else if (passHzStr.length() == 9) {
|
||||
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 4), passHzStr.substring(4, 7), passHzStr.substring(7, 9));
|
||||
} else if (passHzStr.length() == 7) {
|
||||
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 2), passHzStr.substring(2, 5), passHzStr.substring(5, 7));
|
||||
} else if (passHzStr.length() == 6) {
|
||||
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 1), passHzStr.substring(1, 4), passHzStr.substring(4, 6));
|
||||
} else {
|
||||
formattedPassQRG = String.format(Locale.US, "%.1f", passFreqFloat);
|
||||
}
|
||||
long passFrequencyRaw =
|
||||
Long.parseLong(parts.get(11));
|
||||
double passFrequencyKHz =
|
||||
passFrequencyRaw / 10.0;
|
||||
|
||||
if (passFrequencyKHz > 100.0) {
|
||||
formattedPassQrg =
|
||||
helper_formatWinTestFrequency(
|
||||
passFrequencyRaw
|
||||
);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// parts[11] not a valid frequency, leave formattedPassQRG as null
|
||||
} catch (IllegalArgumentException
|
||||
| ArithmeticException ignored) {
|
||||
/*
|
||||
* Token 11 does not contain a usable frequency.
|
||||
* The main frequency remains the safe fallback.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if (this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()) {
|
||||
final String qrgToSet = (this.client.getChatPreferences().isLogsynch_wintestUsePassQrg() && formattedPassQRG != null)
|
||||
? formattedPassQRG
|
||||
: formattedQRG;
|
||||
// JavaFX StringProperty must be updated on the FX Application Thread
|
||||
Platform.runLater(() -> this.client.getChatPreferences().getMYQRGFirstCat().set(qrgToSet));
|
||||
boolean usePassQrg = client
|
||||
.getChatPreferences()
|
||||
.isLogsynch_wintestUsePassQrg();
|
||||
|
||||
final String qrgToSet =
|
||||
usePassQrg && formattedPassQrg != null
|
||||
? formattedPassQrg
|
||||
: formattedMainQrg;
|
||||
|
||||
if (client
|
||||
.getChatPreferences()
|
||||
.isLogsynch_wintestQrgSyncEnabled()) {
|
||||
Platform.runLater(
|
||||
() -> client
|
||||
.getChatPreferences()
|
||||
.getMYQRGFirstCat()
|
||||
.set(qrgToSet)
|
||||
);
|
||||
}
|
||||
|
||||
System.out.println("[WinTest STATUS] stn=" + stn + ", mode=" + mode + ", qrg=" + formattedQRG
|
||||
+ (formattedPassQRG != null ? ", passQrg=" + formattedPassQRG : "")
|
||||
+ ", syncActive=" + this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled());
|
||||
} catch (Exception e) {
|
||||
System.out.println("[WinTest] STATUS parsing error: " + e.getMessage());
|
||||
System.out.println(
|
||||
"[WinTest STATUS] stn=" + stationName
|
||||
+ ", mode=" + mode
|
||||
+ ", qrg=" + formattedMainQrg
|
||||
+ (formattedPassQrg != null
|
||||
? ", passQrg=" + formattedPassQrg
|
||||
: "")
|
||||
+ ", selectedQrg=" + qrgToSet
|
||||
+ ", syncActive="
|
||||
+ client
|
||||
.getChatPreferences()
|
||||
.isLogsynch_wintestQrgSyncEnabled()
|
||||
);
|
||||
} catch (Exception exception) {
|
||||
System.out.println(
|
||||
"[WinTest] STATUS parsing error: "
|
||||
+ exception.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,33 +366,28 @@ public class ReadUDPByWintestThread extends Thread {
|
||||
|
||||
|
||||
/**
|
||||
* Resolves the project Band enum from Win-Test band IDs.
|
||||
* Extracts the unchanged Win-Test band ID from the unquoted ADDQSO fields.
|
||||
*
|
||||
* <p>Only bands that exist in the current Band enum are returned. 50/70 MHz are
|
||||
* still represented as worked flags in ChatMember, but they are not part of the
|
||||
* current Reachability/New-Locator band enum.</p>
|
||||
*
|
||||
* @param bandId Win-Test band id from ADDQSO
|
||||
* @return matching Band or null
|
||||
* @param message complete ADDQSO packet
|
||||
* @return raw band ID or an empty value when the field is missing
|
||||
*/
|
||||
private Band helper_resolveBandFromWinTestBandId(String bandId) {
|
||||
if (bandId == null) {
|
||||
return null;
|
||||
static String extractBandIdFromWinTestAddQso(String message) {
|
||||
if (message == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return switch (bandId.trim()) {
|
||||
case "10" -> Band.B_50;
|
||||
case "11" -> Band.B_70;
|
||||
case "12" -> Band.B_144;
|
||||
case "14" -> Band.B_432;
|
||||
case "16" -> Band.B_1296;
|
||||
case "17" -> Band.B_2320;
|
||||
case "18" -> Band.B_3400;
|
||||
case "19" -> Band.B_5760;
|
||||
case "20" -> Band.B_10G;
|
||||
case "21" -> Band.B_24G;
|
||||
default -> null;
|
||||
};
|
||||
String[] quotedParts = message.split("\"");
|
||||
if (quotedParts.length <= 6) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String unquotedFields = quotedParts[6].trim();
|
||||
if (unquotedFields.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String[] packetFields = unquotedFields.split("\\s+");
|
||||
return packetFields.length > 3 ? packetFields[3] : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,112 +442,32 @@ public class ReadUDPByWintestThread extends Thread {
|
||||
* @param msg
|
||||
*/
|
||||
private void parseAddQso(String msg) {
|
||||
|
||||
|
||||
ChatMember modifyThat = null;
|
||||
|
||||
try {
|
||||
// int qsoNumber = extractQsoNumber(msg);
|
||||
// receivedQsos.put(qsoNumber, msg);
|
||||
// lastKnownQso = Math.max(lastKnownQso, qsoNumber);
|
||||
String callSignCatched = msg.split("\"") [7];
|
||||
String[] quotedParts = msg == null ? new String[0] : msg.split("\"");
|
||||
String callSign = quotedParts.length > 7 ? quotedParts[7] : "";
|
||||
String rawBandId = extractBandIdFromWinTestAddQso(msg);
|
||||
String locatorFromLogger = helper_resolveLocatorFromWinTestAddQso(msg);
|
||||
|
||||
ChatMember workedCall = new ChatMember();
|
||||
workedCall.setCallSign(callSignCatched);
|
||||
workedCall.setWorked(true); //its worked at this place, for sure!
|
||||
|
||||
if (locatorFromLogger != null) {
|
||||
workedCall.setQra(locatorFromLogger);
|
||||
LoggedQsoBand loggedBand = LoggedQsoBand.fromWinTestBandId(rawBandId);
|
||||
ExternalLoggedQso loggedQso = ExternalLoggedQso.create(
|
||||
callSign, loggedBand, locatorFromLogger, "WINTEST").orElse(null);
|
||||
if (loggedQso == null) {
|
||||
System.out.println("[WinTestUDPRcvr: warning] ADDQSO without usable callsign ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<Integer> markTheseChattersAsWorked = client.checkListForChatMemberIndexesByCallSign(workedCall);
|
||||
|
||||
String bandId;
|
||||
bandId = msg.split("\"")[6].split(" ")[4].trim();
|
||||
|
||||
Band workedBand = helper_resolveBandFromWinTestBandId(bandId);
|
||||
switch (bandId) {
|
||||
case "10" -> workedCall.setWorked50(true);
|
||||
case "11" -> workedCall.setWorked70(true);
|
||||
case "12" -> workedCall.setWorked144(true);
|
||||
case "14" -> workedCall.setWorked432(true);
|
||||
case "16" -> workedCall.setWorked1240(true);
|
||||
case "17" -> workedCall.setWorked2300(true);
|
||||
case "18" -> workedCall.setWorked3400(true);
|
||||
case "19" -> workedCall.setWorked5600(true);
|
||||
case "20" -> workedCall.setWorked10G(true);
|
||||
case "21" -> workedCall.setWorked24G(true);
|
||||
case "22" -> workedCall.setWorked47G(true);
|
||||
case "23" -> workedCall.setWorked76G(true);
|
||||
default -> System.out.println("[WinTestUDPRcvr: warning] Unbekannte Band-ID: " + bandId);
|
||||
if (loggedBand == null && !rawBandId.isEmpty()) {
|
||||
System.out.println("[WinTestUDPRcvr: warning] Unknown band ID: " + rawBandId);
|
||||
}
|
||||
|
||||
if (workedBand != null && locatorFromLogger != null) {
|
||||
this.client.registerWorkedGrossField(workedBand, locatorFromLogger, workedCall, "WINTEST");
|
||||
}
|
||||
|
||||
if (!markTheseChattersAsWorked.isEmpty()) {
|
||||
//Worked call is part of the current chatmember list
|
||||
|
||||
for (int index : markTheseChattersAsWorked) {
|
||||
//iterate through the logged in chatmembers callsigns and set the worked markers
|
||||
modifyThat = client.getLst_chatMemberList().get(index);
|
||||
|
||||
modifyThat.setWorked(true); //worked its for sure
|
||||
|
||||
if (locatorFromLogger != null
|
||||
&& (modifyThat.getQra() == null
|
||||
|| modifyThat.getQra().isBlank()
|
||||
|| "unknown".equalsIgnoreCase(modifyThat.getQra()))) {
|
||||
modifyThat.setQra(locatorFromLogger);
|
||||
}
|
||||
|
||||
if (workedCall.isWorked50()) {
|
||||
modifyThat.setWorked50(true);
|
||||
} else if (workedCall.isWorked70()) {
|
||||
modifyThat.setWorked70(true);
|
||||
} else if (workedCall.isWorked144()) {
|
||||
modifyThat.setWorked144(true);
|
||||
} else if (workedCall.isWorked432()) {
|
||||
modifyThat.setWorked432(true);
|
||||
} else if (workedCall.isWorked1240()) {
|
||||
modifyThat.setWorked1240(true);
|
||||
} else if (workedCall.isWorked2300()) {
|
||||
modifyThat.setWorked2300(true);
|
||||
} else if (workedCall.isWorked3400()) {
|
||||
modifyThat.setWorked3400(true);
|
||||
} else if (workedCall.isWorked5600()) {
|
||||
modifyThat.setWorked5600(true);
|
||||
} else if (workedCall.isWorked10G()) {
|
||||
modifyThat.setWorked10G(true);
|
||||
} else if (workedCall.isWorked24G()) {
|
||||
modifyThat.setWorked24G(true);
|
||||
} else if (workedCall.isWorked47G()) {
|
||||
modifyThat.setWorked47G(true);
|
||||
} else if (workedCall.isWorked76G()) {
|
||||
modifyThat.setWorked76G(true);
|
||||
} else {
|
||||
System.out.println("[WinTestUDPRcvr: warning] found no new worked-flag for this band: " + workedCall.getCallSignRaw() + bandId);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
GuiUtils.triggerGUIFilteredChatMemberListChange(client); //not clean at all
|
||||
|
||||
// trigger band-upgrade hint after log entry (Win-Test)
|
||||
try {
|
||||
client.onExternalLogEntryReceived(workedCall.getCallSignRaw());
|
||||
} catch (Exception e) {
|
||||
System.out.println("[WinTestUDPRcvr, warning]: band-upgrade hint failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception IllegalStateException) {
|
||||
//do nothing, as it works...
|
||||
}
|
||||
ChatMember workedCall = loggedQso.toWorkedChatMember();
|
||||
if (loggedBand != null
|
||||
&& loggedBand.getProjectBand() != null
|
||||
&& locatorFromLogger != null) {
|
||||
this.client.registerWorkedGrossField(
|
||||
loggedBand.getProjectBand(), locatorFromLogger, workedCall, loggedQso.getSource());
|
||||
}
|
||||
|
||||
client.applyExternalLoggedQso(loggedQso);
|
||||
|
||||
boolean isInChat = this.client.getDbHandler().updateWkdInfoOnChatMember(workedCall);
|
||||
// This will update the worked info on a worked chatmember. DBHandler will
|
||||
@@ -508,17 +500,9 @@ public class ReadUDPByWintestThread extends Thread {
|
||||
|
||||
bufwrtrRawMSGOut = new BufferedWriter(fileWriterPersistUDPToFile);
|
||||
|
||||
if (modifyThat != null) {
|
||||
bufwrtrRawMSGOut.write("\n" + modifyThat.toString());
|
||||
bufwrtrRawMSGOut.flush();
|
||||
bufwrtrRawMSGOut.close();
|
||||
|
||||
} else {
|
||||
bufwrtrRawMSGOut.write("\n" + workedCall.toString());
|
||||
bufwrtrRawMSGOut.flush();
|
||||
bufwrtrRawMSGOut.close();
|
||||
|
||||
}
|
||||
bufwrtrRawMSGOut.write("\n" + workedCall.toString());
|
||||
bufwrtrRawMSGOut.flush();
|
||||
bufwrtrRawMSGOut.close();
|
||||
|
||||
|
||||
System.out.println("[WinTest, Info: Marking Chatmember as worked: " + workedCall.toString());
|
||||
|
||||
@@ -3,7 +3,6 @@ package kst4contest.controller;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
@@ -13,7 +12,6 @@ import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import kst4contest.ApplicationConstants;
|
||||
import kst4contest.model.ThreadStateMessage;
|
||||
import kst4contest.view.GuiUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -132,54 +130,6 @@ public class ReadUDPbyUCXMessageThread extends Thread {
|
||||
return WorkedGrossFieldCache.extractLocator6(helper_getOptionalElementText(element, "rcvnr"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the project Band enum from logger band values.
|
||||
*
|
||||
* @param band logger band text
|
||||
* @return matching Band or null
|
||||
*/
|
||||
private Band helper_resolveBandFromLoggerBand(String band) {
|
||||
if (band == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (band.trim()) {
|
||||
case "50":
|
||||
case "6m":
|
||||
return Band.B_50;
|
||||
case "70":
|
||||
case "4m":
|
||||
return Band.B_70;
|
||||
case "144":
|
||||
case "2m":
|
||||
return Band.B_144;
|
||||
case "432":
|
||||
case "70cm":
|
||||
return Band.B_432;
|
||||
case "1240":
|
||||
case "1296":
|
||||
case "23cm":
|
||||
return Band.B_1296;
|
||||
case "2300":
|
||||
case "2320":
|
||||
case "13cm":
|
||||
return Band.B_2320;
|
||||
case "3400":
|
||||
case "9cm":
|
||||
return Band.B_3400;
|
||||
case "5600":
|
||||
case "5760":
|
||||
case "6cm":
|
||||
return Band.B_5760;
|
||||
case "10G":
|
||||
case "10368":
|
||||
case "3cm":
|
||||
return Band.B_10G;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
System.out.println("ReadUDPByUCXLogThread: started Thread for UCXLog getUDP");
|
||||
@@ -295,8 +245,6 @@ public class ReadUDPbyUCXMessageThread extends Thread {
|
||||
|
||||
String udpMsg = helper_extractXmlPayload(udpPacketToProcess);
|
||||
|
||||
ChatMember modifyThat = null;
|
||||
|
||||
ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "received Message\n" + udpMsg, false);
|
||||
|
||||
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
|
||||
@@ -342,95 +290,27 @@ public class ReadUDPbyUCXMessageThread extends Thread {
|
||||
|
||||
Element element = (Element) node;
|
||||
|
||||
String call = element.getElementsByTagName("call").item(0).getTextContent();
|
||||
String band = helper_getOptionalElementText(element, "band");
|
||||
String call = helper_getOptionalElementText(element, "call");
|
||||
String rawBand = helper_getOptionalElementText(element, "band");
|
||||
String gridSquare = helper_resolveLocatorFromContactInfo(element);
|
||||
String points = helper_getOptionalElementText(element, "points");
|
||||
LoggedQsoBand loggedBand = LoggedQsoBand.fromLoggerValue(rawBand);
|
||||
ExternalLoggedQso loggedQso = ExternalLoggedQso.create(
|
||||
call, loggedBand, gridSquare, "UCXLOG").orElse(null);
|
||||
if (loggedQso == null) {
|
||||
System.out.println("[ReadUDPFromUCX, warning]: QSO packet without usable callsign ignored");
|
||||
continue;
|
||||
}
|
||||
|
||||
System.out.println("[Readudp, info ]: received Current Element :" + node.getNodeName()
|
||||
+ "call: " + call + " / " + band + " ----> " + points + " POINTS");
|
||||
+ "call: " + call + " / " + rawBand + " ----> " + points + " POINTS");
|
||||
|
||||
// client.getChatPreferences().setBcn_contestScoreSum(Long.parseLong(points));
|
||||
|
||||
ChatMember workedCall = new ChatMember();
|
||||
workedCall.setCallSign(call);
|
||||
workedCall.setWorked(true);
|
||||
|
||||
if (gridSquare != null) {
|
||||
workedCall.setQra(gridSquare);
|
||||
}
|
||||
|
||||
Band workedBand = helper_resolveBandFromLoggerBand(band);
|
||||
|
||||
switch (band) {
|
||||
case "50":
|
||||
case "6m":
|
||||
{
|
||||
workedCall.setWorked50(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "70":
|
||||
case "4m":
|
||||
{
|
||||
workedCall.setWorked70(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "144":
|
||||
case "2m": //minos contest logger
|
||||
{
|
||||
workedCall.setWorked144(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "432":
|
||||
case "70cm":
|
||||
{
|
||||
workedCall.setWorked432(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "1240": //ucxlog style
|
||||
case "1296": //used for n1mm / Dxlog
|
||||
case "23cm": //minos contest logger
|
||||
{
|
||||
workedCall.setWorked1240(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "2300":
|
||||
case "13cm":
|
||||
{
|
||||
workedCall.setWorked2300(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "3400":
|
||||
case "9cm":
|
||||
{
|
||||
workedCall.setWorked3400(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "5600":
|
||||
case "6cm":
|
||||
{
|
||||
workedCall.setWorked5600(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case "10G":
|
||||
case "3cm":
|
||||
{
|
||||
workedCall.setWorked10G(true);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
System.out.println("[ReadUDPFromUCX, Error:] unexpected band value: \"" + band + "\"");
|
||||
break;
|
||||
ChatMember workedCall = loggedQso.toWorkedChatMember();
|
||||
Band workedBand = loggedBand == null ? null : loggedBand.getProjectBand();
|
||||
if (loggedBand == null && !rawBand.isEmpty()) {
|
||||
System.out.println("[ReadUDPFromUCX, warning]: unexpected band value: \"" + rawBand + "\"");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -444,60 +324,7 @@ public class ReadUDPbyUCXMessageThread extends Thread {
|
||||
|
||||
// asd //TODO: Check if callsign and callsignraw is similar, then mark first and further via new checklistforchatmembermultiplemethod with array of indize
|
||||
|
||||
ArrayList<Integer> markTheseChattersAsWorked = client.checkListForChatMemberIndexesByCallSign(workedCall);
|
||||
|
||||
if (markTheseChattersAsWorked.isEmpty()) {
|
||||
//Worked call is not part of the chatmember list
|
||||
} else {
|
||||
for (int index : markTheseChattersAsWorked) {
|
||||
modifyThat = client.getLst_chatMemberList().get(index);
|
||||
|
||||
modifyThat.setWorked(true);
|
||||
|
||||
if (workedCall.isWorked50()) {
|
||||
modifyThat.setWorked50(true);
|
||||
|
||||
} else if (workedCall.isWorked70()) {
|
||||
modifyThat.setWorked70(true);
|
||||
|
||||
} else if (workedCall.isWorked144()) {
|
||||
modifyThat.setWorked144(true);
|
||||
|
||||
} else if (workedCall.isWorked432()) {
|
||||
modifyThat.setWorked432(true);
|
||||
|
||||
} else if (workedCall.isWorked1240()) {
|
||||
modifyThat.setWorked1240(true);
|
||||
|
||||
} else if (workedCall.isWorked2300()) {
|
||||
modifyThat.setWorked2300(true);
|
||||
|
||||
} else if (workedCall.isWorked3400()) {
|
||||
modifyThat.setWorked3400(true);
|
||||
|
||||
} else if (workedCall.isWorked5600()) {
|
||||
modifyThat.setWorked5600(true);
|
||||
|
||||
} else if (workedCall.isWorked10G()) {
|
||||
modifyThat.setWorked10G(true);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
GuiUtils.triggerGUIFilteredChatMemberListChange(this.client);
|
||||
// BEGIN PATCH: trigger band-upgrade hint after log entry (UCXLog)
|
||||
try {
|
||||
client.onExternalLogEntryReceived(workedCall.getCallSignRaw());
|
||||
} catch (Exception e) {
|
||||
System.out.println("[UCXUDPRcvr, warning]: band-upgrade hint failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception IllegalStateException) {
|
||||
//do nothing, as it works...
|
||||
}
|
||||
}
|
||||
client.applyExternalLoggedQso(loggedQso);
|
||||
|
||||
/**
|
||||
* old mechanic to markup worked stations in the chatmember table
|
||||
@@ -574,7 +401,8 @@ public class ReadUDPbyUCXMessageThread extends Thread {
|
||||
}
|
||||
|
||||
if (workedBand != null && gridSquare != null) {
|
||||
this.client.registerWorkedGrossField(workedBand, gridSquare, workedCall, "UCXLOG");
|
||||
this.client.registerWorkedGrossField(
|
||||
workedBand, gridSquare, workedCall, loggedQso.getSource());
|
||||
}
|
||||
|
||||
boolean isInChat = this.client.getDbHandler().updateWkdInfoOnChatMember(workedCall);
|
||||
@@ -609,17 +437,9 @@ public class ReadUDPbyUCXMessageThread extends Thread {
|
||||
|
||||
bufwrtrRawMSGOut = new BufferedWriter(fileWriterPersistUDPToFile);
|
||||
|
||||
if (modifyThat != null) {
|
||||
bufwrtrRawMSGOut.write("\n" + modifyThat.toString());
|
||||
bufwrtrRawMSGOut.flush();
|
||||
bufwrtrRawMSGOut.close();
|
||||
|
||||
} else {
|
||||
bufwrtrRawMSGOut.write("\n" + workedCall.toString());
|
||||
bufwrtrRawMSGOut.flush();
|
||||
bufwrtrRawMSGOut.close();
|
||||
|
||||
}
|
||||
bufwrtrRawMSGOut.write("\n" + workedCall.toString());
|
||||
bufwrtrRawMSGOut.flush();
|
||||
bufwrtrRawMSGOut.close();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -771,4 +591,4 @@ public class ReadUDPbyUCXMessageThread extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import kst4contest.model.ThreadStateMessage;
|
||||
|
||||
public interface StatusUpdateListener {
|
||||
@@ -17,4 +19,29 @@ public interface StatusUpdateListener {
|
||||
void onUserListUpdated(String reason);
|
||||
// new: userlist-update
|
||||
|
||||
/**
|
||||
* Called whenever the authoritative ON4KST session changes lifecycle state.
|
||||
*
|
||||
* <p>The callback may originate from a background connection supervisor. A UI
|
||||
* implementation must marshal control changes onto its application thread.</p>
|
||||
*
|
||||
* @param state new connection, authentication or synchronization state
|
||||
* @param detail human-readable progress or failure reason
|
||||
*/
|
||||
default void onConnectionStateChanged(
|
||||
On4KstConnectionState state,
|
||||
String detail
|
||||
) {
|
||||
// Optional for non-UI listeners.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after KST4Contest successfully creates a missing Simplelogfile.
|
||||
*
|
||||
* @param filePath absolute path of the newly created file
|
||||
*/
|
||||
default void onSimpleLogFileCreated(Path filePath) {
|
||||
// Optional for non-UI listeners.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -14,73 +15,50 @@ import kst4contest.model.ChatMember;
|
||||
|
||||
public class UCXLogFileToHashsetParser {
|
||||
|
||||
public BufferedReader fileReader;
|
||||
// private final String PTRN_CallSign = "(([a-zA-Z]{1,2}[\\d{1}]?\\/)?(\\d{1}[a-zA-Z][\\d{1}][a-zA-Z]{1,3})((\\/p)|(\\/\\d))?)|(([a-zA-Z0-9]{1,2}[\\d{1}]?\\/)?(([a-zA-Z]{1,2}(\\d{1}[a-zA-Z]{1,4})))((\\/p)|(\\/\\d))?)"; //OLD, S51AR for example will not work
|
||||
private final String PTRN_CallSign = "(([a-zA-Z]{1,2}[\\d]{1}?\\/)?(\\d{1}[a-zA-Z][\\d]{1}[a-zA-Z]{1,3})((\\/p)|(\\/\\d))?)|(([a-zA-Z0-9]{1,2}[\\d]{1}?\\/)?(([a-zA-Z]{1,2}(\\d{1}[a-zA-Z]{1,4})))((\\/p)|(\\/\\d))?)|([A-Z]\\d{2}[A-Z]{1,3})";
|
||||
|
||||
private static final Pattern CALL_SIGN_PATTERN = Pattern.compile(
|
||||
"(([a-zA-Z]{1,2}[\\d]{1}?\\/)?(\\d{1}[a-zA-Z][\\d]{1}[a-zA-Z]{1,3})((\\/p)|(\\/\\d))?)"
|
||||
+ "|(([a-zA-Z0-9]{1,2}[\\d]{1}?\\/)?(([a-zA-Z]{1,2}(\\d{1}[a-zA-Z]{1,4})))((\\/p)|(\\/\\d))?)"
|
||||
+ "|([A-Z]\\d{2}[A-Z]{1,3})");
|
||||
|
||||
private final Path logFile;
|
||||
|
||||
public UCXLogFileToHashsetParser(String filePathAndName) {
|
||||
|
||||
try {
|
||||
fileReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePathAndName))));
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.logFile = Path.of(filePathAndName);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a line of the ucxlog-Logfile inhibits a Callsign<br/>
|
||||
* <b>returns ChatMember = null, if no frequency found</b>
|
||||
*
|
||||
* @param chatMessage
|
||||
*/
|
||||
private ChatMember checkIfLineInhibitsCallSign(String line) {
|
||||
|
||||
Pattern pattern = Pattern.compile(PTRN_CallSign);
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
String matchedString = "";
|
||||
private String findLastCallSign(String line) {
|
||||
Matcher matcher = CALL_SIGN_PATTERN.matcher(line);
|
||||
String matchedCallSign = "";
|
||||
|
||||
while (matcher.find()) {
|
||||
|
||||
matchedString = matcher.group();
|
||||
// System.out.println("[UCXLogFile:] Processed worked Callsign from file: " + matchedString);
|
||||
|
||||
matchedCallSign = matcher.group();
|
||||
}
|
||||
|
||||
ChatMember newChatMember = new ChatMember();
|
||||
|
||||
newChatMember.setCallSign(matchedString.toUpperCase());
|
||||
|
||||
return newChatMember;
|
||||
|
||||
return matchedCallSign.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an ucxlog-live-file (full qualified path given by constructor
|
||||
* argument), looks by regex for callsigns and builds a hashmap with only one
|
||||
* entry by callsign
|
||||
* Parses the selected log file and returns every detected station as a
|
||||
* normalized base callsign. The reader is closed after each pass so the logging
|
||||
* application can continue replacing or rotating the file.
|
||||
*
|
||||
* @return unique normalized base callsigns found in the file
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
public HashMap<String, String> parse() throws IOException {
|
||||
public Set<String> parse() throws IOException {
|
||||
Set<String> workedBaseCalls = new HashSet<>();
|
||||
|
||||
HashMap<String, String> chatMemberMap = new HashMap();
|
||||
|
||||
String line;
|
||||
while ((line = fileReader.readLine()) != null) {
|
||||
// System.out.println("raw: " + line);
|
||||
ChatMember temp = checkIfLineInhibitsCallSign(line);
|
||||
|
||||
if (temp.getCallSign() != "") {
|
||||
chatMemberMap.put(temp.getCallSign(), temp.getCallSign());
|
||||
try (BufferedReader fileReader = Files.newBufferedReader(logFile, Charset.defaultCharset())) {
|
||||
String line;
|
||||
while ((line = fileReader.readLine()) != null) {
|
||||
String matchedCallSign = findLastCallSign(line);
|
||||
String baseCallSign = ChatMember.normalizeCallSignToBaseCallSign(matchedCallSign);
|
||||
if (baseCallSign != null && !baseCallSign.isBlank()) {
|
||||
workedBaseCalls.add(baseCallSign.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
}
|
||||
// System.out.println(chatMemberMap.size());
|
||||
return chatMemberMap;
|
||||
|
||||
return workedBaseCalls;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,325 +1,100 @@
|
||||
package kst4contest.controller;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import kst4contest.model.ChatMember;
|
||||
import kst4contest.model.ClusterMessage;
|
||||
import kst4contest.view.GuiUtils;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class UserActualizationTask extends TimerTask {
|
||||
|
||||
private ChatController client;
|
||||
private static final Logger LOGGER = Logger.getLogger(UserActualizationTask.class.getName());
|
||||
|
||||
private final ChatController client;
|
||||
|
||||
public UserActualizationTask(ChatController client) {
|
||||
|
||||
this.client = client;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
Thread.currentThread().setName("UserActualizationTask");
|
||||
|
||||
/*
|
||||
* File-based log synchronization is optional. Do not create, open or parse the
|
||||
* configured file while the feature is disabled.
|
||||
*/
|
||||
try {
|
||||
updateWorkedCallSignsFromFile();
|
||||
} catch (RuntimeException exception) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
"Unexpected failure while updating Worked callsigns from the Simplelogfile; "
|
||||
+ "the periodic task will continue.",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWorkedCallSignsFromFile() {
|
||||
if (!client.getChatPreferences().isLogsynch_fileBasedWkdCallInterpreterEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// System.out.println("[Useract: ] Thread runned now");
|
||||
|
||||
// System.out.println("***********************Useract started");
|
||||
|
||||
/**
|
||||
* ******************************************since here: old mechanic for
|
||||
* marking worked stations by .ucx-file
|
||||
*/
|
||||
|
||||
HashMap<String, String> fetchedWorkedSet = new HashMap<>();
|
||||
// HashMap<String, String> fetchedWorkedSetUdpBckup = new HashMap<>();
|
||||
|
||||
File f = new File(this.client.getChatPreferences().getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly());
|
||||
if (!f.exists() && !f.isDirectory()) {
|
||||
try {
|
||||
f.createNewFile();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
String configuredFileName = client.getChatPreferences()
|
||||
.getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly();
|
||||
if (configuredFileName == null || configuredFileName.isBlank()) {
|
||||
LOGGER.warning("Cannot read the Simplelogfile because no file is selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
UCXLogFileToHashsetParser getWorkedCallsignsOfUCXLogFile = new UCXLogFileToHashsetParser(
|
||||
this.client.getChatPreferences().getLogsynch_fileBasedWkdCallInterpreterFileNameReadOnly());
|
||||
final Path logFile;
|
||||
try {
|
||||
logFile = Path.of(configuredFileName).toAbsolutePath().normalize();
|
||||
} catch (InvalidPathException exception) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
"Cannot use the configured Simplelogfile path: " + configuredFileName,
|
||||
exception);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean created = createMissingLogFile(logFile);
|
||||
if (!Files.isRegularFile(logFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fetchedWorkedSet = getWorkedCallsignsOfUCXLogFile.parse();
|
||||
|
||||
System.out.println("USERACT: fetchedWorkedSet size: " + fetchedWorkedSet.size());
|
||||
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
Set<String> workedBaseCalls = new UCXLogFileToHashsetParser(logFile.toString()).parse();
|
||||
client.applySimpleLogWorkedBaseCalls(workedBaseCalls);
|
||||
LOGGER.log(Level.FINE,
|
||||
"Read {0} unique base callsigns from Simplelogfile {1}.",
|
||||
new Object[] { workedBaseCalls.size(), logFile });
|
||||
} catch (IOException exception) {
|
||||
LOGGER.log(Level.WARNING, "Cannot read Simplelogfile " + logFile, exception);
|
||||
}
|
||||
|
||||
ObservableList<ChatMember> praktiKSTActiveUserList = this.client.getLst_chatMemberList();
|
||||
|
||||
for (Iterator iterator = praktiKSTActiveUserList.iterator(); iterator.hasNext();) {
|
||||
ChatMember chatMember = (ChatMember) iterator.next();
|
||||
|
||||
// System.out.println(chatMember.getCallSign());
|
||||
// System.out.println("USERACT active user list entries " + praktiKSTActiveUserList.size());
|
||||
|
||||
if (fetchedWorkedSet.containsKey(chatMember.getCallSign())) {
|
||||
chatMember.setWorked(true);
|
||||
System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign()
|
||||
+ " as worked, based on READONLY-Logfile.");
|
||||
}
|
||||
|
||||
// if (fetchedWorkedSetUdpBckup.containsKey(chatMember.getCallSign())) {
|
||||
// chatMember.setWorked(true);
|
||||
// System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign() + " as worked, based on UDPLsnBackup-Logfile.");
|
||||
// }
|
||||
// GuiUtils.triggerGUIFilteredChatMemberListChange(this.client); //todo: quick and dirty gui fix
|
||||
if (created) {
|
||||
client.notifySimpleLogFileCreated(logFile);
|
||||
}
|
||||
|
||||
ObservableList<ClusterMessage> praktiKSTClusterList = this.client.getLst_clusterMemberList();
|
||||
|
||||
for (Iterator iterator = praktiKSTClusterList.iterator(); iterator.hasNext();) {
|
||||
ClusterMessage clusterMessage = (ClusterMessage) iterator.next();
|
||||
|
||||
if (fetchedWorkedSet.containsKey(clusterMessage.getReceiver().getCallSign())) {
|
||||
clusterMessage.setReceiverWkd(true);
|
||||
System.out.println("[USERACT, info:] marking Clusterspotted "
|
||||
+ clusterMessage.getReceiver().getCallSign() + " as worked.");
|
||||
}
|
||||
|
||||
// if (fetchedWorkedSetUdpBckup.containsKey(clusterMessage.getReceiver().getCallSign())) {
|
||||
// clusterMessage.setReceiverWkd(true);
|
||||
// System.out.println("[USERACT, info:] marking Clusterspotted "
|
||||
// + clusterMessage.getReceiver().getCallSign() + " as worked.");
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ******************************************end here: old mechanic for marking
|
||||
* worked stations by .ucx-file
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* ******************************************since here: new mechanic for
|
||||
* marking worked stations by udp/adif based information
|
||||
*/
|
||||
// HashMap<String, String> fetchedWorkedMap = new HashMap<>();
|
||||
//
|
||||
// fetchedWorkedMap = this.client.getMap_ucxLogInfoWorkedCalls();
|
||||
|
||||
// ObservableList<ChatMember> praktiKSTActiveUserList1 = this.client.getLst_chatMemberList();
|
||||
//
|
||||
// for (Iterator iterator = praktiKSTActiveUserList.iterator(); iterator.hasNext();) {
|
||||
// ChatMember chatMember = (ChatMember) iterator.next();
|
||||
//
|
||||
// if (fetchedWorkedMap.containsKey(chatMember.getCallSign())) {
|
||||
// chatMember.setWorked(true);
|
||||
// System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign() + " as worked based on UDP Log Info Collector.");
|
||||
// }
|
||||
// }
|
||||
|
||||
// ObservableList<ClusterMessage> praktiKSTClusterList1 = this.client.getLst_clusterMemberList();
|
||||
//
|
||||
// for (Iterator iterator = praktiKSTClusterList.iterator(); iterator.hasNext();) {
|
||||
// ClusterMessage clusterMessage = (ClusterMessage) iterator.next();
|
||||
//
|
||||
// if (fetchedWorkedMap.containsKey(clusterMessage.getReceiver().getCallSign())) {
|
||||
// clusterMessage.setReceiverWkd(true);
|
||||
// System.out.println("[USERACT, info:] marking Clusterspotted "
|
||||
// + clusterMessage.getReceiver().getCallSign() + " as worked based on UDP Log Info Collector.");
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
//
|
||||
// UCXLogFileToHashsetParser getWorkedCallsignsOfUCXLogFile = new UCXLogFileToHashsetParser(
|
||||
// "C:\\UcxLog\\Logs\\DO5AMF\\DVU322_I.UCX");
|
||||
// try {
|
||||
// fetchedWorkedSet = getWorkedCallsignsOfUCXLogFile.parse();
|
||||
// } catch (IOException e) {
|
||||
// // TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
//
|
||||
// ObservableList<ChatMember> praktiKSTActiveUserList = this.client.getLst_chatMemberList();
|
||||
//
|
||||
// for (Iterator iterator = praktiKSTActiveUserList.iterator(); iterator.hasNext();) {
|
||||
// ChatMember chatMember = (ChatMember) iterator.next();
|
||||
//
|
||||
// if (fetchedWorkedSet.containsKey(chatMember.getCallSign())) {
|
||||
// chatMember.setWorked(true);
|
||||
// System.out.println("[USERACT, info:] marking Chatuser " + chatMember.getCallSign() + " as worked.");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// ObservableList<ClusterMessage> praktiKSTClusterList = this.client.getLst_clusterMemberList();
|
||||
//
|
||||
// for (Iterator iterator = praktiKSTClusterList.iterator(); iterator.hasNext();) {
|
||||
// ClusterMessage clusterMessage = (ClusterMessage) iterator.next();
|
||||
//
|
||||
// if (fetchedWorkedSet.containsKey(clusterMessage.getReceiver().getCallSign())) {
|
||||
// clusterMessage.setReceiverWkd(true);
|
||||
// System.out.println("[USERACT, info:] marking Clusterspotted "
|
||||
// + clusterMessage.getReceiver().getCallSign() + " as worked.");
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
/**
|
||||
* ******************************************end here: new mechanic for marking
|
||||
* worked stations by udp/adif based information
|
||||
*
|
||||
*/
|
||||
|
||||
// System.out.println("[UserActualizationtask:] Userlist actualization will be performed now. "
|
||||
// + LocalDateTime.ofInstant(Instant.ofEpochMilli(scheduledExecutionTime()),
|
||||
// ZoneId.systemDefault()));
|
||||
|
||||
// ChatMessage actualizeUserMsg = new ChatMessage();
|
||||
// actualizeUserMsg.setDirectedToServer(true);
|
||||
// actualizeUserMsg.setMessage("/show users");
|
||||
|
||||
// client.getMessageTXBus().add(actualizeUserMsg);
|
||||
|
||||
// Enumeration<String> e = this.client.getChatMemberTable().keys();
|
||||
//
|
||||
// while (e.hasMoreElements()) {
|
||||
// String key = e.nextElement();
|
||||
//
|
||||
// System.out.println(this.client.getChatMemberTable().get(key).getCallSign() + ", "
|
||||
// + this.client.getChatMemberTable().get(key).getQra() + ": "
|
||||
// + this.client.getChatMemberTable().get(key).getFrequency());
|
||||
//
|
||||
// }
|
||||
|
||||
// System.out.println("[UserAct]: Show the Cluster with known frequencies now: ");
|
||||
//
|
||||
// Enumeration<String> e2 = this.client.getdXClusterMemberTable().keys();
|
||||
//
|
||||
// while (e2.hasMoreElements()) {
|
||||
// String key = e2.nextElement();
|
||||
//
|
||||
// System.out.println(this.client.getdXClusterMemberTable().get(key).getCallSign() + ", "
|
||||
// + this.client.getdXClusterMemberTable().get(key).getQra() + ": "
|
||||
// + this.client.getdXClusterMemberTable().get(key).getFrequency());
|
||||
//
|
||||
// }
|
||||
|
||||
// for (int i = 0; i < 100; i++) {
|
||||
//
|
||||
// System.out.print("\n");
|
||||
// }
|
||||
|
||||
/**
|
||||
* keeepalive start
|
||||
*/
|
||||
// ChatMessage keepAliveMSG = new ChatMessage();
|
||||
// keepAliveMSG.setMessageText("\r");
|
||||
// keepAliveMSG.setMessageDirectedToServer(true);
|
||||
//
|
||||
// System.out.println(new Utils4KST().time_generateCurrentMMDDhhmmTimeString() + " [UserAct]: Sending keepalive: "
|
||||
// + keepAliveMSG.getMessageText());
|
||||
// /**
|
||||
// * Sending keepalive
|
||||
// */
|
||||
// this.client.getMessageTXBus().add(keepAliveMSG);
|
||||
|
||||
/**
|
||||
* keeepalive end
|
||||
*/
|
||||
|
||||
// System.out.println("[UserAct]: Show the Userlist with known frequencies sorted now: ");
|
||||
|
||||
// ObservableList<ChatMember> userlist = this.client.getLst_chatMemberList();
|
||||
|
||||
// for (Iterator iterator = userlist.iterator(); iterator.hasNext();) {
|
||||
// ChatMember chatMember = (ChatMember) iterator.next();
|
||||
// System.out.println("[Useract] Entry " + this.client.getLst_chatMemberList().indexOf(chatMember) + ": " + chatMember.getCallSign());
|
||||
// }
|
||||
|
||||
//
|
||||
// String chatMembers ="";
|
||||
|
||||
// SortedSet<String> keys = new TreeSet<>(this.client.getChatMemberTable().keySet());
|
||||
// for (String key : keys) {
|
||||
//
|
||||
// chatMembers += this.client.getChatMemberTable().get(key).getCallSign() + ", "
|
||||
// + this.client.getChatMemberTable().get(key).getName() + " in "
|
||||
// + this.client.getChatMemberTable().get(key).getQra() + " @ QRG: "
|
||||
// + this.client.getChatMemberTable().get(key).getFrequency() + "\n";
|
||||
//
|
||||
// System.out.println(this.client.getChatMemberTable().get(key).getCallSign() + ", "
|
||||
// + this.client.getChatMemberTable().get(key).getName() + " in "
|
||||
// + this.client.getChatMemberTable().get(key).getQra() + " @ QRG: "
|
||||
// + this.client.getChatMemberTable().get(key).getFrequency());
|
||||
// }
|
||||
|
||||
// System.out.println("\n[UserAct]: Show the Clusterlist with known frequencies sorted now: ");
|
||||
|
||||
// String dxcMembers ="";
|
||||
|
||||
// SortedSet<String> keys2 = new TreeSet<>(this.client.getdXClusterMemberTable().keySet());
|
||||
// for (String key : keys2) {
|
||||
// System.out.println(this.client.getdXClusterMemberTable().get(key).getCallSign() + " in "
|
||||
// + this.client.getdXClusterMemberTable().get(key).getQra() + " @ QRG: "
|
||||
// + this.client.getdXClusterMemberTable().get(key).getFrequency());
|
||||
//
|
||||
// dxcMembers += this.client.getdXClusterMemberTable().get(key).getCallSign() + " in "
|
||||
// + this.client.getdXClusterMemberTable().get(key).getQra() + " @ QRG: "
|
||||
// + this.client.getdXClusterMemberTable().get(key).getFrequency();
|
||||
//
|
||||
// }
|
||||
|
||||
// File userListLogger = new File(new Utils4KST().time_generateCurrentMMddString() + "_praktiKST_userlist.txt");
|
||||
//
|
||||
// FileWriter fileWriterRAWChatMSGOut = null;
|
||||
//
|
||||
// try {
|
||||
// fileWriterRAWChatMSGOut = new FileWriter(userListLogger, true);
|
||||
// } catch (IOException e1) {
|
||||
// // TODO Auto-generated catch block
|
||||
// e1.printStackTrace();
|
||||
// }
|
||||
//
|
||||
// BufferedWriter bufwrtrRawMSGOut;
|
||||
//
|
||||
// bufwrtrRawMSGOut = new BufferedWriter(fileWriterRAWChatMSGOut);
|
||||
|
||||
// System.out.println("#######################################" + chatMembers);
|
||||
// try {
|
||||
// bufwrtrRawMSGOut.write(new Utils4KST().time_generateCurrentMMDDhhmmTimeString() + " " +this.client.getChatMemberTable().size() + " Chatmembers:\n" + chatMembers+ "\n");
|
||||
// bufwrtrRawMSGOut.write(new Utils4KST().time_generateCurrentMMDDhhmmTimeString() + " " + this.client.getdXClusterMemberTable().size() + " Clusterentries:\n" + dxcMembers + "\n");
|
||||
|
||||
// bufwrtrRawMSGOut.flush();
|
||||
|
||||
// } catch (IOException e) {
|
||||
// // TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
// try {
|
||||
// bufwrtrRawMSGOut.close();
|
||||
// } catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
}
|
||||
|
||||
private boolean createMissingLogFile(Path logFile) {
|
||||
if (Files.exists(logFile)) {
|
||||
if (!Files.isRegularFile(logFile)) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
"The selected Simplelogfile path is not a regular file: {0}",
|
||||
logFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createFile(logFile);
|
||||
LOGGER.log(Level.INFO, "Created missing Simplelogfile {0}.", logFile);
|
||||
return true;
|
||||
} catch (FileAlreadyExistsException exception) {
|
||||
return false;
|
||||
} catch (IOException | SecurityException exception) {
|
||||
LOGGER.log(Level.WARNING, "Cannot create Simplelogfile " + logFile, exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||