feat(station): drive the motorized antenna from its widget
It showed pattern, elements and Retract — everything except where the antenna is pointed, which is the thing an operator changes most. Tuning it meant opening Settings or moving the rig. A button per band, taken from the bands configured in Settings rather than a list invented here: a band dropped there cannot be clicked here. They point at where people actually work, not the arithmetic centre — 20 m centres on 14175 and nobody lives there, 10 m spans 1.7 MHz of which the top half is empty. Up and down move 25 kHz, which is also the finest tracking threshold: a smaller nudge would be undone by the next poll while tracking is on. They work from the ANTENNA's frequency, not the rig's, or walking the antenna across a band while the rig stays put would be undone on the first press. Tracking moved within reach because it is an operating decision — off to park the antenna, on to resume — not something set up once. Its step only appears when it is on: a threshold for something switched off is a question the operator cannot act on. MotorTuneKHz carries the CURRENT direction rather than resetting it. Both controllers set frequency and pattern in one command, so a bare frequency would silently drop a 180° or bidirectional pattern — a beam turning round is not an acceptable side effect of clicking a band.
This commit is contained in:
@@ -14986,6 +14986,14 @@ type UltrabeamStatusInfo struct {
|
||||
Band int `json:"band"`
|
||||
Moving bool `json:"moving"`
|
||||
Elements []int `json:"elements"` // per-element lengths (mm); empty when unsupported
|
||||
// Follow and StepKHz are mirrored here so the Station Control widget can show
|
||||
// and change tracking without loading the whole settings block for a poll.
|
||||
Follow bool `json:"follow"`
|
||||
StepKHz int `json:"step_khz"`
|
||||
// Bands the antenna is configured to cover — the widget offers exactly these
|
||||
// as buttons rather than inventing its own list, so a band dropped in Settings
|
||||
// cannot be clicked here.
|
||||
Bands []string `json:"bands"`
|
||||
}
|
||||
|
||||
// GetUltrabeamStatus returns the antenna's current state for the UI poll.
|
||||
@@ -14994,6 +15002,9 @@ func (a *App) GetUltrabeamStatus() UltrabeamStatusInfo {
|
||||
s, _ := a.GetUltrabeamSettings()
|
||||
out.Enabled = s.Enabled
|
||||
out.Type = s.Type
|
||||
out.Follow = s.Follow
|
||||
out.StepKHz = s.StepKHz
|
||||
out.Bands = append(out.Bands, s.Bands...)
|
||||
if a.motorAnt == nil {
|
||||
return out
|
||||
}
|
||||
@@ -15051,6 +15062,71 @@ func (a *App) SetUltrabeamDirection(direction int) error {
|
||||
return a.motorAnt.SetDirection(direction)
|
||||
}
|
||||
|
||||
// MotorTuneKHz points the antenna at a frequency, in kHz.
|
||||
//
|
||||
// The direction is carried over from the antenna's current state rather than
|
||||
// reset: both controllers set frequency and pattern in the same command, so
|
||||
// sending a bare frequency would silently drop a 180° or bidirectional pattern
|
||||
// the operator had chosen — a beam quietly turning round is not an acceptable
|
||||
// side effect of clicking a band.
|
||||
func (a *App) MotorTuneKHz(khz int) error {
|
||||
if a.motorAnt == nil {
|
||||
return fmt.Errorf("antenna not connected — enable it in Settings → Antenna")
|
||||
}
|
||||
// A generous envelope rather than a band table: the antennas differ, some
|
||||
// carry extensions, and refusing a frequency the hardware would have accepted
|
||||
// is worse than letting the controller say no itself.
|
||||
if khz < 1000 || khz > 60000 {
|
||||
return fmt.Errorf("frequency %d kHz is outside anything these antennas tune", khz)
|
||||
}
|
||||
a.noteMotorMoveCommanded()
|
||||
return a.motorAnt.SetFrequency(khz, a.motorAnt.Status().Direction)
|
||||
}
|
||||
|
||||
// MotorNudgeKHz moves the antenna's commanded frequency by a step, up or down.
|
||||
//
|
||||
// Works from the ANTENNA's frequency, not the rig's: this exists so an operator
|
||||
// can walk the antenna across a band while the rig stays where it is, and
|
||||
// starting from the rig would undo that on the first press. Falls back to the
|
||||
// rig only when the antenna has not reported a frequency yet.
|
||||
func (a *App) MotorNudgeKHz(deltaKHz int) error {
|
||||
if a.motorAnt == nil {
|
||||
return fmt.Errorf("antenna not connected — enable it in Settings → Antenna")
|
||||
}
|
||||
cur := a.motorAnt.Status().Frequency
|
||||
if cur <= 0 && a.cat != nil {
|
||||
if rs := a.cat.State(); rs.Connected && rs.FreqHz > 0 {
|
||||
cur = int(rs.FreqHz / 1000)
|
||||
}
|
||||
}
|
||||
if cur <= 0 {
|
||||
return fmt.Errorf("the antenna has not reported a frequency yet")
|
||||
}
|
||||
return a.MotorTuneKHz(cur + deltaKHz)
|
||||
}
|
||||
|
||||
// SetMotorFollow turns frequency tracking on or off and sets its step, without
|
||||
// opening Settings. Both are ordinary operating decisions — an operator turns
|
||||
// tracking off to park the antenna and back on to resume — and a preferences
|
||||
// dialog is the wrong place for something changed that often.
|
||||
func (a *App) SetMotorFollow(on bool, stepKHz int) error {
|
||||
s, err := a.GetUltrabeamSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch stepKHz {
|
||||
case 25, 50, 100:
|
||||
s.StepKHz = stepKHz
|
||||
case 0: // leave the step alone
|
||||
default:
|
||||
return fmt.Errorf("step must be 25, 50 or 100 kHz")
|
||||
}
|
||||
s.Follow = on
|
||||
// SaveUltrabeamSettings restarts the client, which is what actually starts or
|
||||
// stops the follow loop — the setting alone would not.
|
||||
return a.SaveUltrabeamSettings(s)
|
||||
}
|
||||
|
||||
// UltrabeamRetract retracts all elements (storage / safe position).
|
||||
func (a *App) UltrabeamRetract() error {
|
||||
if a.motorAnt == nil {
|
||||
|
||||
+4
-2
@@ -8,7 +8,8 @@
|
||||
"Shared CAT is steadier and now diagnoses itself. It survives a rig answering \"busy\" just after transmit instead of dropping the link, stops repeating a PTT state the client never changed, and logs a plain explanation when another program has taken its port or when a client is set to a rig model instead of Hamlib NET rigctl. It also answers the lock-mode and stop-morse commands some clients send around every transmit, instead of refusing them.",
|
||||
"Web publishing now offers every field a QSO carries, awards included — 123 instead of 23 — from a searchable dropdown, with the chosen columns listed above it in publication order. Choose carefully: the page is public and the list includes addresses and e-mail.",
|
||||
"Band-opening detection now works on the bands it was meant for. Switching on \"Watch for band openings\" (Settings › DX Cluster) subscribes to the PSK Reporter feed and adds the two RBN nodes it needs — it was reading a handful of VHF cluster spots while thousands of stations were reporting. It watches 10, 6, 4 and 2 m, counts only reports collected within 300 km of you so an opening means an opening HERE, no longer throws away paths beyond 2400 km, and looks for the busiest direction instead of demanding that every station heard sit in one sector. An opening under way shows as a blinking badge beside the clock until the band goes quiet, and the direction it names is right — a sector crossing north used to be announced as its opposite.",
|
||||
"Test connection now actually tests. Club Log checked that the three fields were not empty and reported success without contacting anyone, so a wrong password looked exactly like a right one; it now signs in for real, through the read-only endpoint so a test can never add a record. LoTW reports its two credentials separately: TQSL signs uploads and never uses the website password, so a wrong one used to break nothing until the day confirmations were downloaded."
|
||||
"Test connection now actually tests. Club Log checked that the three fields were not empty and reported success without contacting anyone, so a wrong password looked exactly like a right one; it now signs in for real, through the read-only endpoint so a test can never add a record. LoTW reports its two credentials separately: TQSL signs uploads and never uses the website password, so a wrong one used to break nothing until the day confirmations were downloaded.",
|
||||
"The Ultrabeam / SteppIR widget in Station Control can now drive the antenna, not just describe it: a button per configured band tunes it there, up and down move it 25 kHz at a time, and tracking can be switched on or off with its 25 / 50 / 100 kHz threshold beside it. The band you are on lights up, and the commanded frequency is shown in the header."
|
||||
],
|
||||
"fr": [
|
||||
"Décodes digitaux : le carré locator d une station pouvait être enregistré comme son indicatif quand le texte du message sortait de l ordinaire. Un grid à la place de l indicatif est maintenant refusé.",
|
||||
@@ -16,7 +17,8 @@
|
||||
"Le CAT partagé est plus solide et se diagnostique tout seul. Il survit à un rig qui répond « occupé » juste après une émission au lieu de lâcher le lien, cesse de répéter un état PTT que le client n a pas changé, et écrit une explication claire quand un autre programme lui a pris son port ou qu un client est réglé sur un modèle de rig au lieu de Hamlib NET rigctl. Il répond aussi aux commandes de verrouillage et d arrêt du morse que certains logiciels envoient à chaque émission, au lieu de les refuser.",
|
||||
"La publication web propose désormais tous les champs d un QSO, awards compris — 123 au lieu de 23 — depuis une liste déroulante cherchable, les colonnes choisies étant listées au-dessus dans l ordre de publication. À choisir avec soin : la page est publique et la liste contient adresses et e-mails.",
|
||||
"La détection d ouverture de bande fonctionne enfin sur les bandes visées. Activer « Surveiller les ouvertures de bande » (Paramètres › Cluster DX) souscrit au flux PSK Reporter et ajoute les deux nœuds RBN nécessaires — elle lisait quelques spots VHF du cluster alors que des milliers de stations rapportaient. Elle surveille le 10, 6, 4 et 2 m, ne retient que les reports collectés à moins de 300 km de toi pour qu une ouverture veuille dire une ouverture ICI, ne jette plus les chemins au-delà de 2400 km, et cherche la direction la plus dense au lieu d exiger que toutes les stations entendues soient dans un même secteur. Une ouverture en cours s affiche en pastille clignotante à côté de l heure jusqu à ce que la bande se taise, et la direction annoncée est juste — un secteur traversant le nord était auparavant annoncé à l opposé.",
|
||||
"Le bouton Tester la connexion teste vraiment. Club Log vérifiait que les trois champs n étaient pas vides et annonçait la réussite sans contacter personne : un mauvais mot de passe ressemblait exactement à un bon. Il s authentifie maintenant pour de vrai, via le point d accès en lecture seule pour qu un test ne puisse jamais ajouter un enregistrement. LoTW annonce ses deux identifiants séparément : TQSL signe les envois et n utilise jamais le mot de passe du site, donc un mauvais ne cassait rien jusqu au jour du téléchargement des confirmations."
|
||||
"Le bouton Tester la connexion teste vraiment. Club Log vérifiait que les trois champs n étaient pas vides et annonçait la réussite sans contacter personne : un mauvais mot de passe ressemblait exactement à un bon. Il s authentifie maintenant pour de vrai, via le point d accès en lecture seule pour qu un test ne puisse jamais ajouter un enregistrement. LoTW annonce ses deux identifiants séparément : TQSL signe les envois et n utilise jamais le mot de passe du site, donc un mauvais ne cassait rien jusqu au jour du téléchargement des confirmations.",
|
||||
"Le widget Ultrabeam / SteppIR de Station Control pilote enfin l antenne au lieu de seulement la décrire : un bouton par bande configurée l accorde dessus, haut et bas la déplacent par pas de 25 kHz, et le suivi s active ou se coupe avec son seuil de 25 / 50 / 100 kHz à côté. La bande courante s allume, et la fréquence commandée s affiche dans l en-tête."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { Plus, Pencil, Trash2, Power, PlugZap, Loader2, Check, X, Compass, Square, Antenna as AntennaIcon, ArrowDownToLine, Minus, RefreshCw, GripVertical } from 'lucide-react';
|
||||
import { Plus, Pencil, Trash2, Power, PlugZap, Loader2, Check, X, Compass, Square, Antenna as AntennaIcon, ArrowDownToLine, Minus, RefreshCw, GripVertical, ChevronUp, ChevronDown } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
GetStationDevices, SaveStationDevices, GetStationStatus, StationSetRelay,
|
||||
GetRotatorHeading, RotatorGoTo, RotatorStop, SetActiveRotor,
|
||||
GetUltrabeamStatus, SetUltrabeamDirection, UltrabeamRetract, MotorSetElement, MotorReadElements,
|
||||
MotorTuneKHz, MotorNudgeKHz, SetMotorFollow,
|
||||
ListDenkoviDevices, ListSerialPorts, TestStationDevice,
|
||||
GetAmpStatuses, GetFlexState,
|
||||
GetTunerGeniusStatus, GetTunerGeniusSettings,
|
||||
@@ -115,7 +116,37 @@ function RotatorWidget({ hd, refetch, centerLat, centerLon, bearing, t }: Rotato
|
||||
);
|
||||
}
|
||||
|
||||
type AntStatus = { enabled: boolean; type: string; connected: boolean; direction: number; frequency: number; moving: boolean; elements: number[] };
|
||||
type AntStatus = { enabled: boolean; type: string; connected: boolean; direction: number; frequency: number; moving: boolean; elements: number[]; follow?: boolean; step_khz?: number; bands?: string[] };
|
||||
|
||||
// Where each band button points the antenna.
|
||||
//
|
||||
// Not the arithmetic centre of the band. An antenna is tuned for where people
|
||||
// actually work: 20 m centres on 14175 but nobody lives there, and 10 m spans
|
||||
// 1.7 MHz of which the top half is empty. These are the points that leave the
|
||||
// elements closest to right for the whole band, and one nudge away from the rest.
|
||||
const ANT_BAND_KHZ: Record<string, number> = {
|
||||
'40m': 7100, '30m': 10125, '20m': 14150, '17m': 18110,
|
||||
'15m': 21150, '12m': 24930, '10m': 28400, '6m': 50150,
|
||||
};
|
||||
|
||||
// NUDGE_KHZ is the up/down step. 25 kHz because that is also the finest tracking
|
||||
// threshold: a nudge smaller than the tracking step would be undone by the next
|
||||
// poll while tracking is on.
|
||||
const NUDGE_KHZ = 25;
|
||||
|
||||
// bandOfKHz names the band a frequency sits in, so a band button can light up
|
||||
// when the ANTENNA is already there. Deliberately generous at the edges: the
|
||||
// antenna's reported frequency is where it was commanded, which can sit slightly
|
||||
// outside the allocation.
|
||||
function bandOfKHz(khz: number): string {
|
||||
const edges: [number, number, string][] = [
|
||||
[6900, 7300, '40m'], [10050, 10200, '30m'], [13900, 14400, '20m'],
|
||||
[18000, 18200, '17m'], [20900, 21500, '15m'], [24800, 25000, '12m'],
|
||||
[27900, 29800, '10m'], [49900, 50600, '6m'],
|
||||
];
|
||||
for (const [lo, hi, b] of edges) if (khz >= lo && khz <= hi) return b;
|
||||
return '';
|
||||
}
|
||||
|
||||
// MotorAntennaWidget controls a motorized antenna (Ultrabeam / SteppIR) from the
|
||||
// Station Control tab: pattern (Normal / 180° / Bi), Retract, and — Ultrabeam
|
||||
@@ -203,6 +234,70 @@ function MotorAntennaWidget({ ant, refetch, t }: { ant: AntStatus; refetch: () =
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Bands, then a nudge, then tracking — in the order an operator uses
|
||||
them: get to the band, fine-tune inside it, decide whether the
|
||||
antenna should follow the rig from here. */}
|
||||
<div>
|
||||
<div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground mb-1">{t('station.bands')}</div>
|
||||
<div className="grid grid-cols-5 gap-1">
|
||||
{(ant.bands ?? []).map((b: string) => {
|
||||
const khz = ANT_BAND_KHZ[b];
|
||||
if (!khz) return null;
|
||||
// "On this band" from the antenna's own frequency, not the rig's:
|
||||
// the widget must show where the ANTENNA is, which is the whole
|
||||
// reason for tuning it by hand.
|
||||
const here = ant.frequency > 0 && bandOfKHz(ant.frequency) === b;
|
||||
return (
|
||||
<button key={b} type="button" disabled={!ant.connected}
|
||||
onClick={() => run(MotorTuneKHz(khz))}
|
||||
title={`${(khz / 1000).toFixed(3)} MHz`}
|
||||
className={cn('rounded-md border py-1 text-[11px] font-mono font-semibold transition-colors disabled:opacity-40',
|
||||
here ? 'bg-primary text-primary-foreground border-primary' : 'border-border hover:bg-muted')}>
|
||||
{b}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<button type="button" disabled={!ant.connected}
|
||||
onClick={() => run(MotorNudgeKHz(-NUDGE_KHZ))}
|
||||
title={t('station.nudgeDown', { n: NUDGE_KHZ })}
|
||||
className="flex-1 flex items-center justify-center gap-1 rounded-md border border-border py-1.5 text-xs font-semibold hover:bg-muted disabled:opacity-40">
|
||||
<ChevronDown className="size-3.5" /> {NUDGE_KHZ}
|
||||
</button>
|
||||
<button type="button" disabled={!ant.connected}
|
||||
onClick={() => run(MotorNudgeKHz(NUDGE_KHZ))}
|
||||
title={t('station.nudgeUp', { n: NUDGE_KHZ })}
|
||||
className="flex-1 flex items-center justify-center gap-1 rounded-md border border-border py-1.5 text-xs font-semibold hover:bg-muted disabled:opacity-40">
|
||||
<ChevronUp className="size-3.5" /> {NUDGE_KHZ}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tracking. Here rather than only in Settings because it is an operating
|
||||
decision — off to park the antenna, on to resume — not something set
|
||||
up once. The step only shows when tracking is on: a threshold for
|
||||
something switched off is a question the operator cannot act on. */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button type="button"
|
||||
onClick={() => run(SetMotorFollow(!ant.follow, 0))}
|
||||
className={cn('flex-1 rounded-md border py-1.5 text-xs font-semibold transition-colors',
|
||||
ant.follow ? 'bg-success-muted text-success-muted-foreground border-success-border' : 'border-border hover:bg-muted')}>
|
||||
{ant.follow ? t('station.trackOn') : t('station.trackOff')}
|
||||
</button>
|
||||
{ant.follow && (
|
||||
<select
|
||||
value={String(ant.step_khz || 50)}
|
||||
onChange={(e) => run(SetMotorFollow(true, parseInt(e.target.value, 10)))}
|
||||
className="h-[30px] rounded-md border border-border bg-background px-1 text-xs font-mono"
|
||||
title={t('station.trackStepTip')}
|
||||
>
|
||||
{[25, 50, 100].map((s) => <option key={s} value={s}>{s} kHz</option>)}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button type="button" disabled={!ant.connected}
|
||||
onClick={() => run(UltrabeamRetract())}
|
||||
className="w-full flex items-center justify-center gap-1.5 rounded-md border border-warning-border bg-warning-muted text-warning-muted-foreground py-1.5 text-xs font-semibold hover:brightness-95 disabled:opacity-40">
|
||||
|
||||
@@ -155,7 +155,7 @@ const en: Dict = {
|
||||
'uscty.backfillIntro': 'Resolve county (and grid) for US QSOs already in your log that are missing them. Existing values are kept — only blanks are filled.',
|
||||
'uscty.backfillRun': 'Fill missing counties',
|
||||
'uscty.backfillDone': '{s} US QSOs scanned · {c} counties, {g} grids filled.',
|
||||
'station.title': 'Station Control', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'No heading read', 'station.pattern': 'Pattern', 'station.bi': 'Bi', 'station.retract': 'Retract elements', 'station.moving': 'MOVING', 'station.elements': 'Elements (mm)', 'station.read': 'Read', 'station.readLengths': 'Read current element lengths from the controller', 'station.noLengths': 'Lengths unknown — click Read to fetch them from the controller.', 'station.element': 'Element', 'station.reflector': 'Reflector', 'station.driven': 'Driven', 'station.director': 'Dir', 'station.set': 'Set', 'station.elementsHint': 'Each press lengthens/shortens the element by 2 mm (like the physical console). Verify which element responds on your antenna.', 'station.setExactLen': 'Click to type the exact current length (fixes the baseline if the auto-read is off).', 'station.atMax': 'Controller refused — the element is likely at its maximum length for this band, so it can\'t extend further.', 'station.go': 'Go', 'station.stop': 'Stop', 'station.dragHint': 'Drag the grip handle on the left of a panel to move it. Pick a column count to lay them out in a grid.', 'station.dragMove': 'Drag to move this panel', 'station.colsAuto': 'Auto', 'station.addDevice': 'Add device', 'station.editDevice': 'Edit device', 'station.empty': 'No relay boards yet. Add a WebSwitch 1216H or a KMTronic 8-relay board to control your station power and accessories.', 'station.online': 'Online', 'station.offline': 'Offline', 'station.edit': 'Edit', 'station.delete': 'Delete', 'station.relay': 'Relay', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': 'Device type', 'station.name': 'Name', 'station.host': 'Host / IP', 'station.hostHint': 'LAN IP for local use. To reach the board from OUTSIDE, put a full URL here — e.g. https://relay.yourdomain.com — pointing at a reverse proxy (Nginx Proxy Manager…) that fronts the board’s HTTP port.', 'station.user': 'Username', 'station.pass': 'Password', 'station.optional': 'optional', 'station.ftdiSerial': 'FTDI serial number', 'station.detect': 'Detect', 'station.ftdiHint': 'The Denkovi board is driven via FTDI bit-bang (not the COM port). Pick its serial (e.g. DAE0006K). Needs the FTDI D2XX driver installed.', 'station.channels': 'Relays', 'station.comPort': 'COM port', 'station.noPorts': 'No ports found', 'station.usbRelayHint': 'Cheap USB-serial relay boards (CH340/LCUS) using the A0 command protocol. If yours does not switch, tell me its model / command set.', 'station.labels': 'Relay labels', 'station.cancel': 'Cancel', 'station.save': 'Save', 'station.test': 'Test connection', 'station.testOk': 'Connected — {n} relays', 'station.testFail': 'Not connected', 'station.detectNone': 'No FTDI board found — check the cable and that the D2XX driver is installed.', 'station.detectFound': '{n} board(s) detected.',
|
||||
'station.title': 'Station Control', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'No heading read', 'station.bands': 'Bands', 'station.nudgeUp': 'Up {n} kHz', 'station.nudgeDown': 'Down {n} kHz', 'station.trackOn': 'Tracking on', 'station.trackOff': 'Tracking off', 'station.trackStepTip': 'Re-tune only when the rig has moved this far', 'station.pattern': 'Pattern', 'station.bi': 'Bi', 'station.retract': 'Retract elements', 'station.moving': 'MOVING', 'station.elements': 'Elements (mm)', 'station.read': 'Read', 'station.readLengths': 'Read current element lengths from the controller', 'station.noLengths': 'Lengths unknown — click Read to fetch them from the controller.', 'station.element': 'Element', 'station.reflector': 'Reflector', 'station.driven': 'Driven', 'station.director': 'Dir', 'station.set': 'Set', 'station.elementsHint': 'Each press lengthens/shortens the element by 2 mm (like the physical console). Verify which element responds on your antenna.', 'station.setExactLen': 'Click to type the exact current length (fixes the baseline if the auto-read is off).', 'station.atMax': 'Controller refused — the element is likely at its maximum length for this band, so it can\'t extend further.', 'station.go': 'Go', 'station.stop': 'Stop', 'station.dragHint': 'Drag the grip handle on the left of a panel to move it. Pick a column count to lay them out in a grid.', 'station.dragMove': 'Drag to move this panel', 'station.colsAuto': 'Auto', 'station.addDevice': 'Add device', 'station.editDevice': 'Edit device', 'station.empty': 'No relay boards yet. Add a WebSwitch 1216H or a KMTronic 8-relay board to control your station power and accessories.', 'station.online': 'Online', 'station.offline': 'Offline', 'station.edit': 'Edit', 'station.delete': 'Delete', 'station.relay': 'Relay', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': 'Device type', 'station.name': 'Name', 'station.host': 'Host / IP', 'station.hostHint': 'LAN IP for local use. To reach the board from OUTSIDE, put a full URL here — e.g. https://relay.yourdomain.com — pointing at a reverse proxy (Nginx Proxy Manager…) that fronts the board’s HTTP port.', 'station.user': 'Username', 'station.pass': 'Password', 'station.optional': 'optional', 'station.ftdiSerial': 'FTDI serial number', 'station.detect': 'Detect', 'station.ftdiHint': 'The Denkovi board is driven via FTDI bit-bang (not the COM port). Pick its serial (e.g. DAE0006K). Needs the FTDI D2XX driver installed.', 'station.channels': 'Relays', 'station.comPort': 'COM port', 'station.noPorts': 'No ports found', 'station.usbRelayHint': 'Cheap USB-serial relay boards (CH340/LCUS) using the A0 command protocol. If yours does not switch, tell me its model / command set.', 'station.labels': 'Relay labels', 'station.cancel': 'Cancel', 'station.save': 'Save', 'station.test': 'Test connection', 'station.testOk': 'Connected — {n} relays', 'station.testFail': 'Not connected', 'station.detectNone': 'No FTDI board found — check the cable and that the D2XX driver is installed.', 'station.detectFound': '{n} board(s) detected.',
|
||||
'awards.followHint': 'Awards shown in the Awards tab. Leave the right side empty to show them all.', 'awards.available': 'All awards', 'awards.followed': 'Followed', 'awards.search': 'Filter…', 'awards.addAll': 'Add all', 'awards.clear': 'Clear', 'awards.allTracked': 'All awards are followed.', 'awards.noneFollowed': 'Nothing followed yet — the Awards tab shows all.',
|
||||
'sec.awards': 'Awards', 'sec.cat': 'CAT interface', 'sec.rotator': 'Rotator', 'sec.winkeyer': 'CW Keyer',
|
||||
'sec.antenna': 'Ultrabeam / Steppir', 'sec.antgenius': 'Antenna Genius', 'sec.tunergenius': 'Tuner Genius', 'sec.pgxl': 'Amplifier', 'sec.flex': 'FlexRadio', 'sec.audio': 'Audio devices',
|
||||
@@ -582,7 +582,7 @@ const fr: Dict = {
|
||||
'uscty.backfillIntro': "Résout le comté (et le locator) pour les QSO US déjà dans ton log qui n'en ont pas. Les valeurs existantes sont conservées — seuls les vides sont remplis.",
|
||||
'uscty.backfillRun': 'Remplir les comtés manquants',
|
||||
'uscty.backfillDone': '{s} QSO US analysés · {c} comtés, {g} locators remplis.',
|
||||
'station.title': 'Contrôle station', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'Azimut non lu', 'station.pattern': 'Diagramme', 'station.bi': 'Bi', 'station.retract': 'Rétracter les éléments', 'station.moving': 'EN MOUVEMENT', 'station.elements': 'Éléments (mm)', 'station.read': 'Lire', 'station.readLengths': 'Lire les longueurs actuelles depuis le contrôleur', 'station.noLengths': 'Longueurs inconnues — clique sur Lire pour les récupérer depuis le contrôleur.', 'station.element': 'Élément', 'station.reflector': 'Réflecteur', 'station.driven': 'Radiateur', 'station.director': 'Dir', 'station.set': 'Régler', 'station.elementsHint': "Chaque appui allonge/raccourcit l'élément de 2 mm (comme le pupitre). Vérifie quel élément répond sur ton antenne.", 'station.setExactLen': "Clique pour taper la longueur actuelle exacte (recale la base si la lecture auto est fausse).", 'station.atMax': "Refusé par le contrôleur — l'élément est probablement en butée (longueur max pour cette bande), il ne peut plus s'allonger.", 'station.go': 'Aller', 'station.stop': 'Stop', 'station.dragHint': 'Glisse la poignée à gauche d un panneau pour le déplacer. Choisis un nombre de colonnes pour la disposition.', 'station.dragMove': 'Glisser pour déplacer ce panneau', 'station.colsAuto': 'Auto', 'station.addDevice': 'Ajouter un appareil', 'station.editDevice': "Modifier l'appareil", 'station.empty': "Aucune carte relais. Ajoute un WebSwitch 1216H ou une carte KMTronic 8 relais pour piloter l'alimentation et les accessoires de ta station.", 'station.online': 'En ligne', 'station.offline': 'Hors ligne', 'station.edit': 'Modifier', 'station.delete': 'Supprimer', 'station.relay': 'Relais', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': "Type d'appareil", 'station.name': 'Nom', 'station.host': 'Hôte / IP', 'station.hostHint': "IP du LAN en local. Pour joindre la carte depuis L'EXTÉRIEUR, saisis une URL complète ici — ex. https://relais.tondomaine.com — pointant vers un reverse proxy (Nginx Proxy Manager…) qui expose le port HTTP de la carte.", 'station.user': "Nom d'utilisateur", 'station.pass': 'Mot de passe', 'station.optional': 'optionnel', 'station.ftdiSerial': 'Numéro de série FTDI', 'station.detect': 'Détecter', 'station.ftdiHint': "La carte Denkovi se pilote en FTDI bit-bang (pas via le port COM). Choisis son numéro de série (ex. DAE0006K). Nécessite le driver FTDI D2XX installé.", 'station.channels': 'Relais', 'station.comPort': 'Port COM', 'station.noPorts': 'Aucun port', 'station.usbRelayHint': "Cartes USB-série bon marché (CH340/LCUS) protocole A0. Si la tienne ne commute pas, donne-moi le modèle / jeu de commandes.", 'station.labels': 'Libellés des relais', 'station.cancel': 'Annuler', 'station.save': 'Enregistrer', 'station.test': 'Tester la connexion', 'station.testOk': 'Connecté — {n} relais', 'station.testFail': 'Non connecté', 'station.detectNone': 'Aucune carte FTDI trouvée — vérifie le câble et que le driver D2XX est installé.', 'station.detectFound': '{n} carte(s) détectée(s).',
|
||||
'station.title': 'Contrôle station', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'Azimut non lu', 'station.bands': 'Bandes', 'station.nudgeUp': 'Monter de {n} kHz', 'station.nudgeDown': 'Descendre de {n} kHz', 'station.trackOn': 'Suivi actif', 'station.trackOff': 'Suivi inactif', 'station.trackStepTip': 'Ne réaccorder que si le rig a bougé d au moins ça', 'station.pattern': 'Diagramme', 'station.bi': 'Bi', 'station.retract': 'Rétracter les éléments', 'station.moving': 'EN MOUVEMENT', 'station.elements': 'Éléments (mm)', 'station.read': 'Lire', 'station.readLengths': 'Lire les longueurs actuelles depuis le contrôleur', 'station.noLengths': 'Longueurs inconnues — clique sur Lire pour les récupérer depuis le contrôleur.', 'station.element': 'Élément', 'station.reflector': 'Réflecteur', 'station.driven': 'Radiateur', 'station.director': 'Dir', 'station.set': 'Régler', 'station.elementsHint': "Chaque appui allonge/raccourcit l'élément de 2 mm (comme le pupitre). Vérifie quel élément répond sur ton antenne.", 'station.setExactLen': "Clique pour taper la longueur actuelle exacte (recale la base si la lecture auto est fausse).", 'station.atMax': "Refusé par le contrôleur — l'élément est probablement en butée (longueur max pour cette bande), il ne peut plus s'allonger.", 'station.go': 'Aller', 'station.stop': 'Stop', 'station.dragHint': 'Glisse la poignée à gauche d un panneau pour le déplacer. Choisis un nombre de colonnes pour la disposition.', 'station.dragMove': 'Glisser pour déplacer ce panneau', 'station.colsAuto': 'Auto', 'station.addDevice': 'Ajouter un appareil', 'station.editDevice': "Modifier l'appareil", 'station.empty': "Aucune carte relais. Ajoute un WebSwitch 1216H ou une carte KMTronic 8 relais pour piloter l'alimentation et les accessoires de ta station.", 'station.online': 'En ligne', 'station.offline': 'Hors ligne', 'station.edit': 'Modifier', 'station.delete': 'Supprimer', 'station.relay': 'Relais', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': "Type d'appareil", 'station.name': 'Nom', 'station.host': 'Hôte / IP', 'station.hostHint': "IP du LAN en local. Pour joindre la carte depuis L'EXTÉRIEUR, saisis une URL complète ici — ex. https://relais.tondomaine.com — pointant vers un reverse proxy (Nginx Proxy Manager…) qui expose le port HTTP de la carte.", 'station.user': "Nom d'utilisateur", 'station.pass': 'Mot de passe', 'station.optional': 'optionnel', 'station.ftdiSerial': 'Numéro de série FTDI', 'station.detect': 'Détecter', 'station.ftdiHint': "La carte Denkovi se pilote en FTDI bit-bang (pas via le port COM). Choisis son numéro de série (ex. DAE0006K). Nécessite le driver FTDI D2XX installé.", 'station.channels': 'Relais', 'station.comPort': 'Port COM', 'station.noPorts': 'Aucun port', 'station.usbRelayHint': "Cartes USB-série bon marché (CH340/LCUS) protocole A0. Si la tienne ne commute pas, donne-moi le modèle / jeu de commandes.", 'station.labels': 'Libellés des relais', 'station.cancel': 'Annuler', 'station.save': 'Enregistrer', 'station.test': 'Tester la connexion', 'station.testOk': 'Connecté — {n} relais', 'station.testFail': 'Non connecté', 'station.detectNone': 'Aucune carte FTDI trouvée — vérifie le câble et que le driver D2XX est installé.', 'station.detectFound': '{n} carte(s) détectée(s).',
|
||||
'awards.followHint': 'Diplômes affichés dans l’onglet Awards. Laisse la colonne de droite vide pour tous les afficher.', 'awards.available': 'Tous les diplômes', 'awards.followed': 'Suivis', 'awards.search': 'Filtrer…', 'awards.addAll': 'Tout ajouter', 'awards.clear': 'Vider', 'awards.allTracked': 'Tous les diplômes sont suivis.', 'awards.noneFollowed': 'Aucun pour l’instant — l’onglet Awards les montre tous.',
|
||||
'sec.awards': 'Diplômes', 'sec.cat': 'Interface CAT', 'sec.rotator': 'Rotator', 'sec.winkeyer': 'Manipulateur CW',
|
||||
'sec.antenna': 'Antenne motorisée', 'sec.antgenius': 'Antenna Genius', 'sec.tunergenius': 'Tuner Genius', 'sec.pgxl': 'Amplificateur', 'sec.flex': 'FlexRadio', 'sec.audio': 'Périphériques audio',
|
||||
|
||||
Vendored
+6
@@ -682,10 +682,14 @@ export function LookupCallsign(arg1:string):Promise<lookup.Result>;
|
||||
|
||||
export function LookupCallsignFresh(arg1:string):Promise<lookup.Result>;
|
||||
|
||||
export function MotorNudgeKHz(arg1:number):Promise<void>;
|
||||
|
||||
export function MotorReadElements():Promise<Array<number>>;
|
||||
|
||||
export function MotorSetElement(arg1:number,arg2:number):Promise<void>;
|
||||
|
||||
export function MotorTuneKHz(arg1:number):Promise<void>;
|
||||
|
||||
export function MoveDatabase(arg1:string):Promise<void>;
|
||||
|
||||
export function NetActivate(arg1:string):Promise<qso.QSO>;
|
||||
@@ -976,6 +980,8 @@ export function SetDVKLabel(arg1:number,arg2:string):Promise<void>;
|
||||
|
||||
export function SetKenwoodKeySpeed(arg1:number):Promise<void>;
|
||||
|
||||
export function SetMotorFollow(arg1:boolean,arg2:number):Promise<void>;
|
||||
|
||||
export function SetOpsLogQSLReceived(arg1:number,arg2:boolean):Promise<void>;
|
||||
|
||||
export function SetPassphrase(arg1:string):Promise<void>;
|
||||
|
||||
@@ -1306,6 +1306,10 @@ export function LookupCallsignFresh(arg1) {
|
||||
return window['go']['main']['App']['LookupCallsignFresh'](arg1);
|
||||
}
|
||||
|
||||
export function MotorNudgeKHz(arg1) {
|
||||
return window['go']['main']['App']['MotorNudgeKHz'](arg1);
|
||||
}
|
||||
|
||||
export function MotorReadElements() {
|
||||
return window['go']['main']['App']['MotorReadElements']();
|
||||
}
|
||||
@@ -1314,6 +1318,10 @@ export function MotorSetElement(arg1, arg2) {
|
||||
return window['go']['main']['App']['MotorSetElement'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function MotorTuneKHz(arg1) {
|
||||
return window['go']['main']['App']['MotorTuneKHz'](arg1);
|
||||
}
|
||||
|
||||
export function MoveDatabase(arg1) {
|
||||
return window['go']['main']['App']['MoveDatabase'](arg1);
|
||||
}
|
||||
@@ -1894,6 +1902,10 @@ export function SetKenwoodKeySpeed(arg1) {
|
||||
return window['go']['main']['App']['SetKenwoodKeySpeed'](arg1);
|
||||
}
|
||||
|
||||
export function SetMotorFollow(arg1, arg2) {
|
||||
return window['go']['main']['App']['SetMotorFollow'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SetOpsLogQSLReceived(arg1, arg2) {
|
||||
return window['go']['main']['App']['SetOpsLogQSLReceived'](arg1, arg2);
|
||||
}
|
||||
|
||||
@@ -3300,6 +3300,9 @@ export namespace main {
|
||||
band: number;
|
||||
moving: boolean;
|
||||
elements: number[];
|
||||
follow: boolean;
|
||||
step_khz: number;
|
||||
bands: string[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new UltrabeamStatusInfo(source);
|
||||
@@ -3315,6 +3318,9 @@ export namespace main {
|
||||
this.band = source["band"];
|
||||
this.moving = source["moving"];
|
||||
this.elements = source["elements"];
|
||||
this.follow = source["follow"];
|
||||
this.step_khz = source["step_khz"];
|
||||
this.bands = source["bands"];
|
||||
}
|
||||
}
|
||||
export class UpdateInfo {
|
||||
|
||||
Reference in New Issue
Block a user