feat(rotator): add Hy-Gain DCU-1 backend (RotorCard DXA, Rotor-EZ, Green Heron)
New internal/rotator/dcu1 client speaking the DCU-1 command set (AP1nnn / AM1 to go-to, AI1 to read bearing), over a serial COM port (4800 baud default) or a raw TCP serial-over-IP bridge. Azimuth-only; Stop re-commands the current bearing since the base set has no stop opcode. Wired through app.go (normRotorType, default port 4001, GoTo/Heading/Stop/test switches, park returns the same "PstRotator only" note as ARCO) and exposed in the rotor settings as "Hy-Gain DCU-1 (RotorCard DXA, Rotor-EZ, Green Heron)" with the ARCO's serial/TCP transport chooser and a bilingual hint. Protocol is implemented from the standard DCU-1 spec and still needs field confirmation against a real RotorCard DXA.
This commit is contained in:
@@ -52,6 +52,7 @@ import (
|
|||||||
"hamlog/internal/qso"
|
"hamlog/internal/qso"
|
||||||
"hamlog/internal/relaydev"
|
"hamlog/internal/relaydev"
|
||||||
"hamlog/internal/rigctld"
|
"hamlog/internal/rigctld"
|
||||||
|
"hamlog/internal/rotator/dcu1"
|
||||||
"hamlog/internal/rotator/gs232"
|
"hamlog/internal/rotator/gs232"
|
||||||
"hamlog/internal/rotator/pst"
|
"hamlog/internal/rotator/pst"
|
||||||
"hamlog/internal/rotgenius"
|
"hamlog/internal/rotgenius"
|
||||||
@@ -13477,7 +13478,7 @@ type logicalRotor struct {
|
|||||||
|
|
||||||
// normRotorType clamps a rotor type to a known backend.
|
// normRotorType clamps a rotor type to a known backend.
|
||||||
func normRotorType(t string) string {
|
func normRotorType(t string) string {
|
||||||
if t == "rotgenius" || t == "arco" {
|
if t == "rotgenius" || t == "arco" || t == "dcu1" {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
return "pst"
|
return "pst"
|
||||||
@@ -13490,6 +13491,8 @@ func rotatorDefaultPort(typ string) int {
|
|||||||
return 9006 // 4O3A native default
|
return 9006 // 4O3A native default
|
||||||
case "arco":
|
case "arco":
|
||||||
return 4001 // placeholder — the real number is set in ARCO's LAN menu
|
return 4001 // placeholder — the real number is set in ARCO's LAN menu
|
||||||
|
case "dcu1":
|
||||||
|
return 4001 // only used with a serial-over-IP bridge; DCU-1 has no standard
|
||||||
default:
|
default:
|
||||||
return 12000 // PstRotator UDP
|
return 12000 // PstRotator UDP
|
||||||
}
|
}
|
||||||
@@ -13682,6 +13685,16 @@ func arcoClient(l rotorLink) *gs232.Client {
|
|||||||
return gs232.New(l.Host, l.Port)
|
return gs232.New(l.Host, l.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dcu1Client builds the Hy-Gain DCU-1 client for a rotor's transport: the
|
||||||
|
// controller's COM port (the usual case — RotorCard DXA, Green Heron, Rotor-EZ)
|
||||||
|
// or a serial-over-IP bridge on TCP.
|
||||||
|
func dcu1Client(l rotorLink) *dcu1.Client {
|
||||||
|
if l.Transport == "serial" {
|
||||||
|
return dcu1.NewSerial(l.ComPort, l.Baud)
|
||||||
|
}
|
||||||
|
return dcu1.New(l.Host, l.Port)
|
||||||
|
}
|
||||||
|
|
||||||
// RotatorHeading is the live antenna heading for the status bar and compass.
|
// RotatorHeading is the live antenna heading for the status bar and compass.
|
||||||
type RotatorHeading struct {
|
type RotatorHeading struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
@@ -13748,6 +13761,16 @@ func (a *App) GetRotatorHeading() RotatorHeading {
|
|||||||
base.Azimuth = az
|
base.Azimuth = az
|
||||||
base.Raw = raw
|
base.Raw = raw
|
||||||
return base
|
return base
|
||||||
|
case "dcu1":
|
||||||
|
az, raw, herr := dcu1Client(link).Heading()
|
||||||
|
if herr != nil {
|
||||||
|
base.Raw = herr.Error()
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
base.OK = true
|
||||||
|
base.Azimuth = az
|
||||||
|
base.Raw = raw
|
||||||
|
return base
|
||||||
default:
|
default:
|
||||||
az, raw, herr := pst.New(link.Host, link.Port).Heading()
|
az, raw, herr := pst.New(link.Host, link.Port).Heading()
|
||||||
if herr != nil {
|
if herr != nil {
|
||||||
@@ -13774,6 +13797,8 @@ func (a *App) RotatorGoTo(az int, el int) error {
|
|||||||
return rotgenius.New(link.Host, link.Port).GoTo(link.Num, az)
|
return rotgenius.New(link.Host, link.Port).GoTo(link.Num, az)
|
||||||
case "arco":
|
case "arco":
|
||||||
return arcoClient(link).GoTo(az)
|
return arcoClient(link).GoTo(az)
|
||||||
|
case "dcu1":
|
||||||
|
return dcu1Client(link).GoTo(az)
|
||||||
default:
|
default:
|
||||||
return pst.New(link.Host, link.Port).GoTo(az, link.HasElevation, el)
|
return pst.New(link.Host, link.Port).GoTo(az, link.HasElevation, el)
|
||||||
}
|
}
|
||||||
@@ -13791,6 +13816,8 @@ func (a *App) RotatorStop() error {
|
|||||||
return rotgenius.New(link.Host, link.Port).Stop()
|
return rotgenius.New(link.Host, link.Port).Stop()
|
||||||
case "arco":
|
case "arco":
|
||||||
return arcoClient(link).Stop()
|
return arcoClient(link).Stop()
|
||||||
|
case "dcu1":
|
||||||
|
return dcu1Client(link).Stop()
|
||||||
default:
|
default:
|
||||||
return pst.New(link.Host, link.Port).Stop()
|
return pst.New(link.Host, link.Port).Stop()
|
||||||
}
|
}
|
||||||
@@ -13809,6 +13836,8 @@ func (a *App) RotatorPark() error {
|
|||||||
return fmt.Errorf("park is a PstRotator feature; not available on the Rotator Genius")
|
return fmt.Errorf("park is a PstRotator feature; not available on the Rotator Genius")
|
||||||
case "arco":
|
case "arco":
|
||||||
return fmt.Errorf("park is a PstRotator feature; not available over the ARCO GS-232 link")
|
return fmt.Errorf("park is a PstRotator feature; not available over the ARCO GS-232 link")
|
||||||
|
case "dcu1":
|
||||||
|
return fmt.Errorf("park is a PstRotator feature; not available over the DCU-1 link")
|
||||||
default:
|
default:
|
||||||
return pst.New(link.Host, link.Port).Park()
|
return pst.New(link.Host, link.Port).Park()
|
||||||
}
|
}
|
||||||
@@ -13847,6 +13876,14 @@ func testRotorLink(l rotorLink) error {
|
|||||||
// GS-232 — without moving the antenna.
|
// GS-232 — without moving the antenna.
|
||||||
_, _, err := arcoClient(l).Heading()
|
_, _, err := arcoClient(l).Heading()
|
||||||
return err
|
return err
|
||||||
|
case "dcu1":
|
||||||
|
if l.Transport == "serial" && strings.TrimSpace(l.ComPort) == "" {
|
||||||
|
return fmt.Errorf("select the DCU-1 controller's COM port first")
|
||||||
|
}
|
||||||
|
// A bearing query (AI1) proves the link and the DCU-1 command set without
|
||||||
|
// moving the antenna.
|
||||||
|
_, _, err := dcu1Client(l).Heading()
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return pst.New(l.Host, l.Port).GoTo(0, false, -1)
|
return pst.New(l.Host, l.Port).GoTo(0, false, -1)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -8,7 +8,8 @@
|
|||||||
"Performance: fixed a slowdown introduced in 0.23.6. To dim the \"represents nothing\" spots, the DX Cluster grid was redrawing EVERY row on each spot-status update, which pegged the CPU on a busy cluster (some PCs became sluggish). The dimming now updates with a light per-cell refresh instead — same look, no more churn.",
|
"Performance: fixed a slowdown introduced in 0.23.6. To dim the \"represents nothing\" spots, the DX Cluster grid was redrawing EVERY row on each spot-status update, which pegged the CPU on a busy cluster (some PCs became sluggish). The dimming now updates with a light per-cell refresh instead — same look, no more churn.",
|
||||||
"DX Cluster: after you log a QSO, the spots update — a callsign, entity, POTA, prefix or band/mode slot you just worked stops showing its NEW badge, instead of staying \"new\" until a restart. Kept fast: it re-evaluates only while the cluster is actually on screen (nothing runs otherwise — a QSO logged while it's hidden refreshes once when you next open it), and it's debounced so a quick run doesn't re-scan on every QSO.",
|
"DX Cluster: after you log a QSO, the spots update — a callsign, entity, POTA, prefix or band/mode slot you just worked stops showing its NEW badge, instead of staying \"new\" until a restart. Kept fast: it re-evaluates only while the cluster is actually on screen (nothing runs otherwise — a QSO logged while it's hidden refreshes once when you next open it), and it's debounced so a quick run doesn't re-scan on every QSO.",
|
||||||
"Station Control relays (WebSwitch / KMTronic / Dingtian): the Host field now accepts a full URL, so you can reach a network relay board through an HTTPS reverse proxy — e.g. https://relay.yourdomain.com.",
|
"Station Control relays (WebSwitch / KMTronic / Dingtian): the Host field now accepts a full URL, so you can reach a network relay board through an HTTPS reverse proxy — e.g. https://relay.yourdomain.com.",
|
||||||
"Stats slot drill-down: the pop-up listing the QSOs behind a band/mode square looks tidier (banded rows, cleaner header), and you can now click a callsign in it to open that QSO for editing."
|
"Stats slot drill-down: the pop-up listing the QSOs behind a band/mode square looks tidier (banded rows, cleaner header), and you can now click a callsign in it to open that QSO for editing.",
|
||||||
|
"DCU-1 rotor support: OpsLog can now steer controllers that speak the Hy-Gain DCU-1 protocol (RotorCard DXA for Yaesu DXA rotors, Idiom Press Rotor-EZ, Green Heron) over their COM port or a serial-over-IP bridge."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"Édition de QSO : ajout du bouton QRZ ↗ à côté de l'indicatif, comme dans le formulaire de saisie — un clic ouvre le profil qrz.com de la station.",
|
"Édition de QSO : ajout du bouton QRZ ↗ à côté de l'indicatif, comme dans le formulaire de saisie — un clic ouvre le profil qrz.com de la station.",
|
||||||
@@ -16,7 +17,8 @@
|
|||||||
"Performance : correction d'un ralentissement apparu en 0.23.6. Pour atténuer les spots « qui ne représentent rien », la grille du DX Cluster redessinait TOUTES les lignes à chaque mise à jour de statut, ce qui saturait le CPU sur un cluster actif (des PC devenaient lents). L'atténuation se met désormais à jour via un rafraîchissement léger par cellule — même rendu, sans le brassage.",
|
"Performance : correction d'un ralentissement apparu en 0.23.6. Pour atténuer les spots « qui ne représentent rien », la grille du DX Cluster redessinait TOUTES les lignes à chaque mise à jour de statut, ce qui saturait le CPU sur un cluster actif (des PC devenaient lents). L'atténuation se met désormais à jour via un rafraîchissement léger par cellule — même rendu, sans le brassage.",
|
||||||
"DX Cluster : après avoir loggué un QSO, les spots se mettent à jour — un indicatif, une entité, un POTA, un préfixe ou un slot bande/mode que vous venez de contacter cesse d'afficher son badge NEW, au lieu de rester « new » jusqu'au redémarrage. Reste rapide : la réévaluation n'a lieu que si le cluster est affiché (sinon rien ne tourne — un QSO loggué cluster caché se rafraîchit une fois à sa réouverture), et c'est débouncé pour ne pas re-scanner à chaque QSO en série.",
|
"DX Cluster : après avoir loggué un QSO, les spots se mettent à jour — un indicatif, une entité, un POTA, un préfixe ou un slot bande/mode que vous venez de contacter cesse d'afficher son badge NEW, au lieu de rester « new » jusqu'au redémarrage. Reste rapide : la réévaluation n'a lieu que si le cluster est affiché (sinon rien ne tourne — un QSO loggué cluster caché se rafraîchit une fois à sa réouverture), et c'est débouncé pour ne pas re-scanner à chaque QSO en série.",
|
||||||
"Relais du Contrôle station (WebSwitch / KMTronic / Dingtian) : le champ Hôte accepte désormais une URL complète ex. https://relais.tondomaine.com derrière Nginx Proxy Manager. Avant, OpsLog forçait http://<hôte>, donc une carte sur le LAN derrière un proxy (quand vos 80/443 vont déjà ailleurs) était injoignable de l'extérieur.",
|
"Relais du Contrôle station (WebSwitch / KMTronic / Dingtian) : le champ Hôte accepte désormais une URL complète ex. https://relais.tondomaine.com derrière Nginx Proxy Manager. Avant, OpsLog forçait http://<hôte>, donc une carte sur le LAN derrière un proxy (quand vos 80/443 vont déjà ailleurs) était injoignable de l'extérieur.",
|
||||||
"Détail d’un slot Stats : la fenêtre listant les QSO derrière une case bande/mode est plus soignée (lignes alternées, en-tête plus propre), et vous pouvez maintenant cliquer un indicatif pour ouvrir ce QSO en édition."
|
"Détail d’un slot Stats : la fenêtre listant les QSO derrière une case bande/mode est plus soignée (lignes alternées, en-tête plus propre), et vous pouvez maintenant cliquer un indicatif pour ouvrir ce QSO en édition.",
|
||||||
|
"Prise en charge des rotors DCU-1 : OpsLog pilote désormais les contrôleurs parlant le protocole Hy-Gain DCU-1 (RotorCard DXA pour rotors Yaesu DXA, Idiom Press Rotor-EZ, Green Heron) via leur port COM ou un pont série-sur-IP."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -887,4 +889,4 @@
|
|||||||
"Ce résumé « Nouveautés » s'affiche désormais au premier lancement après chaque mise à jour."
|
"Ce résumé « Nouveautés » s'affiche désormais au premier lancement après chaque mise à jour."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3388,6 +3388,8 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
const dev = d as any;
|
const dev = d as any;
|
||||||
const isRG = dev.type === 'rotgenius';
|
const isRG = dev.type === 'rotgenius';
|
||||||
const isARCO = dev.type === 'arco';
|
const isARCO = dev.type === 'arco';
|
||||||
|
const isDCU1 = dev.type === 'dcu1';
|
||||||
|
const isSerialCap = isARCO || isDCU1; // COM-port or serial-over-IP controllers
|
||||||
const transport = dev.transport ?? 'tcp';
|
const transport = dev.transport ?? 'tcp';
|
||||||
return (
|
return (
|
||||||
<div key={dev.id || i} className="rounded-xl border border-border bg-card/40 p-3 space-y-3">
|
<div key={dev.id || i} className="rounded-xl border border-border bg-card/40 p-3 space-y-3">
|
||||||
@@ -3406,12 +3408,13 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
{/* Each backend gets its default port: Rotator Genius 9006, ARCO 4001
|
{/* Each backend gets its default port: Rotator Genius 9006, ARCO 4001
|
||||||
(placeholder — must match the ARCO's LAN menu), PstRotator 12000. */}
|
(placeholder — must match the ARCO's LAN menu), PstRotator 12000. */}
|
||||||
<Select value={dev.type ?? 'pst'}
|
<Select value={dev.type ?? 'pst'}
|
||||||
onValueChange={(v) => patch(i, { type: v as any, port: v === 'rotgenius' ? 9006 : v === 'arco' ? 4001 : 12000 })}>
|
onValueChange={(v) => patch(i, { type: v as any, port: v === 'rotgenius' ? 9006 : (v === 'arco' || v === 'dcu1') ? 4001 : 12000, ...(v === 'dcu1' ? { transport: 'serial' } : {}) })}>
|
||||||
<SelectTrigger className="h-9"><SelectValue /></SelectTrigger>
|
<SelectTrigger className="h-9"><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="pst">PstRotator (UDP)</SelectItem>
|
<SelectItem value="pst">PstRotator (UDP)</SelectItem>
|
||||||
<SelectItem value="rotgenius">Rotator Genius (4O3A, native)</SelectItem>
|
<SelectItem value="rotgenius">Rotator Genius (4O3A, native)</SelectItem>
|
||||||
<SelectItem value="arco">GS-232A controller (microHAM ARCO, ERC…)</SelectItem>
|
<SelectItem value="arco">GS-232A controller (microHAM ARCO, ERC…)</SelectItem>
|
||||||
|
<SelectItem value="dcu1">Hy-Gain DCU-1 (RotorCard DXA, Rotor-EZ, Green Heron)</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
@@ -3428,8 +3431,8 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* The ARCO is reachable over the LAN (TCP) or its USB virtual COM. */}
|
{/* ARCO and DCU-1 controllers reach over the LAN (TCP) or a serial COM. */}
|
||||||
{isARCO && (
|
{isSerialCap && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label>Connection</Label>
|
<Label>Connection</Label>
|
||||||
<Select value={transport} onValueChange={(v) => patch(i, { transport: v as any })}>
|
<Select value={transport} onValueChange={(v) => patch(i, { transport: v as any })}>
|
||||||
@@ -3449,7 +3452,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
{t('rot.rgDual')}
|
{t('rot.rgDual')}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
{isARCO && transport === 'serial' ? (
|
{isSerialCap && transport === 'serial' ? (
|
||||||
<div className="space-y-1 max-w-xs">
|
<div className="space-y-1 max-w-xs">
|
||||||
<Label>COM port</Label>
|
<Label>COM port</Label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -3478,16 +3481,16 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
<div className="space-y-1 col-span-2">
|
<div className="space-y-1 col-span-2">
|
||||||
<Label>Host / IP</Label>
|
<Label>Host / IP</Label>
|
||||||
<Input value={dev.host ?? ''} onChange={(e) => patch(i, { host: e.target.value })}
|
<Input value={dev.host ?? ''} onChange={(e) => patch(i, { host: e.target.value })}
|
||||||
placeholder={isRG || isARCO ? '192.168.1.60' : '127.0.0.1'} className="font-mono" />
|
placeholder={isRG || isSerialCap ? '192.168.1.60' : '127.0.0.1'} className="font-mono" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label>{isRG || isARCO ? 'TCP port' : 'UDP port'}</Label>
|
<Label>{isRG || isSerialCap ? 'TCP port' : 'UDP port'}</Label>
|
||||||
<PortInput value={dev.port} onChange={(n) => patch(i, { port: n })}
|
<PortInput value={dev.port} onChange={(n) => patch(i, { port: n })}
|
||||||
fallback={isRG ? 9006 : isARCO ? 4001 : 12000} className="font-mono" />
|
fallback={isRG ? 9006 : isSerialCap ? 4001 : 12000} className="font-mono" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isRG && !isARCO && (
|
{!isRG && !isSerialCap && (
|
||||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||||
<Checkbox checked={!!dev.has_elevation} onCheckedChange={(c) => patch(i, { has_elevation: !!c })} />
|
<Checkbox checked={!!dev.has_elevation} onCheckedChange={(c) => patch(i, { has_elevation: !!c })} />
|
||||||
This rotator supports elevation (VHF / satellite)
|
This rotator supports elevation (VHF / satellite)
|
||||||
@@ -3495,6 +3498,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
)}
|
)}
|
||||||
{isRG && <p className="text-xs text-muted-foreground">{t('rot.rgHint')}</p>}
|
{isRG && <p className="text-xs text-muted-foreground">{t('rot.rgHint')}</p>}
|
||||||
{isARCO && <p className="text-xs text-muted-foreground">{t('rot.arcoHint')}</p>}
|
{isARCO && <p className="text-xs text-muted-foreground">{t('rot.arcoHint')}</p>}
|
||||||
|
{isDCU1 && <p className="text-xs text-muted-foreground">{t('rot.dcu1Hint')}</p>}
|
||||||
{/* Which antenna this rotor carries — only relevant with >1 rotor. */}
|
{/* Which antenna this rotor carries — only relevant with >1 rotor. */}
|
||||||
{multi && (
|
{multi && (
|
||||||
<div className="space-y-1 max-w-xs">
|
<div className="space-y-1 max-w-xs">
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ const en: Dict = {
|
|||||||
'cat.hint': "Reads the rig's frequency / band / mode and pushes them into the entry strip in real time. Use OmniRig (free, any rig) or — for FlexRadio — the native SmartSDR API (no OmniRig needed, real-time, no second-click mode bug).",
|
'cat.hint': "Reads the rig's frequency / band / mode and pushes them into the entry strip in real time. Use OmniRig (free, any rig) or — for FlexRadio — the native SmartSDR API (no OmniRig needed, real-time, no second-click mode bug).",
|
||||||
'ag2.hint': 'OpsLog talks to the 4O3A Antenna Genius switch over TCP (GSCP protocol). The port is fixed at 9007, so only the device IP is needed. A docked widget then lets you switch antennas per port (A/B).', 'ag2.password': 'Remote password', 'ag2.passwordPh': 'blank on LAN', 'ag2.passwordHint': 'Only needed when reaching the device remotely — it then announces "AG AUTH" and rejects commands until you log in. Leave blank on the local network.',
|
'ag2.hint': 'OpsLog talks to the 4O3A Antenna Genius switch over TCP (GSCP protocol). The port is fixed at 9007, so only the device IP is needed. A docked widget then lets you switch antennas per port (A/B).', 'ag2.password': 'Remote password', 'ag2.passwordPh': 'blank on LAN', 'ag2.passwordHint': 'Only needed when reaching the device remotely — it then announces "AG AUTH" and rejects commands until you log in. Leave blank on the local network.',
|
||||||
'tg2.hint': 'OpsLog talks to the 4O3A Tuner Genius XL over TCP (port fixed at 9010), so only the device IP is needed. A docked widget then shows SWR and forward power and offers Tune, Bypass and Operate/Standby. Control it directly (not through the radio) so OpsLog uses just one of the box\'s connection slots.', 'tg2.enable': 'Enable Tuner Genius control', 'tg2.portHint': 'The TCP port is fixed at 9010 on the device.', 'tg2.password': 'Remote code', 'tg2.passwordPh': 'blank on LAN', 'tg2.passwordHint': 'Only needed when reaching the device remotely — it then announces "AUTH" and rejects commands until you log in. Leave blank on the local network.',
|
'tg2.hint': 'OpsLog talks to the 4O3A Tuner Genius XL over TCP (port fixed at 9010), so only the device IP is needed. A docked widget then shows SWR and forward power and offers Tune, Bypass and Operate/Standby. Control it directly (not through the radio) so OpsLog uses just one of the box\'s connection slots.', 'tg2.enable': 'Enable Tuner Genius control', 'tg2.portHint': 'The TCP port is fixed at 9010 on the device.', 'tg2.password': 'Remote code', 'tg2.passwordPh': 'blank on LAN', 'tg2.passwordHint': 'Only needed when reaching the device remotely — it then announces "AUTH" and rejects commands until you log in. Leave blank on the local network.',
|
||||||
'rot.enable': 'Enable rotator control', 'rot.testOkRG': 'Connected — the Rotator Genius accepted the command (moving to 0°).', 'rot.type': 'Rotator type', 'rot.rotatorNum': 'Rotator #', 'rot.dual': 'Two rotors', 'rot.rotor1': 'Rotor 1', 'rot.rotor2': 'Rotor 2', 'rot.name': 'Name', 'rot.antenna': 'Antenna', 'rot.antenna1': 'Rotor 1 antenna', 'rot.antenna2': 'Rotor 2 antenna', 'rot.name1': 'Rotor 1 name', 'rot.name2': 'Rotor 2 name', 'rot.antStd': 'Standard antenna', 'rot.antMotor': 'Motorized (Ultrabeam/SteppIR)', 'rot.add': 'Add rotor', 'rot.remove': 'Remove rotor', 'rot.none': 'No rotor configured. Click “Add rotor” to add one.', 'rot.rgDual': 'This Rotator Genius drives two rotors (adds a second one)', 'rot.testRotor1': 'Test rotor 1', 'rot.testRotor2': 'Test rotor 2', 'rot.dualHint': 'Two independent rotors of any type — for example an ARCO and a Rotator Genius side by side. Configure each below; OpsLog shows a Rotor 1/2 toggle on the compass to pick which one it turns and displays. Set each rotor to "Motorized" so the boom/pattern paths (reverse, bidirectional) show only for the one that carries the Ultrabeam/SteppIR. (For a single Rotator Genius driving two rotors, set both to Rotator Genius with the same host and rotator # 1 and 2.)', 'rot.rgHint': 'Talks directly to a 4O3A Rotator Genius over TCP (default port 9006) — no PstRotator needed. Rotator # selects which of the two rotators to drive.', 'rot.arcoHint': "Talks directly to any controller set to Yaesu GS-232A — no PstRotator needed. microHAM ARCO: set Config → LAN → CONTROL PROTOCOL (or USB CONTROL PROTOCOL) to 'Yaesu GS-232A'; on USB the baud rate does not matter. ERC (Easy Rotor Control, incl. ERC Mini): its emulation MUST be set to GS-232 — an ERC left on Hy-Gain DCU-1 speaks a different command set and will not answer — then pick its COM port and the same baud rate as in the ERC configuration.", 'rot.hint': "OpsLog sends UDP commands to PstRotator. Enable PstRotator's UDP listener (Setup → Communication → UDP) before testing.",
|
'rot.enable': 'Enable rotator control', 'rot.testOkRG': 'Connected — the Rotator Genius accepted the command (moving to 0°).', 'rot.type': 'Rotator type', 'rot.rotatorNum': 'Rotator #', 'rot.dual': 'Two rotors', 'rot.rotor1': 'Rotor 1', 'rot.rotor2': 'Rotor 2', 'rot.name': 'Name', 'rot.antenna': 'Antenna', 'rot.antenna1': 'Rotor 1 antenna', 'rot.antenna2': 'Rotor 2 antenna', 'rot.name1': 'Rotor 1 name', 'rot.name2': 'Rotor 2 name', 'rot.antStd': 'Standard antenna', 'rot.antMotor': 'Motorized (Ultrabeam/SteppIR)', 'rot.add': 'Add rotor', 'rot.remove': 'Remove rotor', 'rot.none': 'No rotor configured. Click “Add rotor” to add one.', 'rot.rgDual': 'This Rotator Genius drives two rotors (adds a second one)', 'rot.testRotor1': 'Test rotor 1', 'rot.testRotor2': 'Test rotor 2', 'rot.dualHint': 'Two independent rotors of any type — for example an ARCO and a Rotator Genius side by side. Configure each below; OpsLog shows a Rotor 1/2 toggle on the compass to pick which one it turns and displays. Set each rotor to "Motorized" so the boom/pattern paths (reverse, bidirectional) show only for the one that carries the Ultrabeam/SteppIR. (For a single Rotator Genius driving two rotors, set both to Rotator Genius with the same host and rotator # 1 and 2.)', 'rot.rgHint': 'Talks directly to a 4O3A Rotator Genius over TCP (default port 9006) — no PstRotator needed. Rotator # selects which of the two rotators to drive.', 'rot.arcoHint': "Talks directly to any controller set to Yaesu GS-232A — no PstRotator needed. microHAM ARCO: set Config → LAN → CONTROL PROTOCOL (or USB CONTROL PROTOCOL) to 'Yaesu GS-232A'; on USB the baud rate does not matter. ERC (Easy Rotor Control, incl. ERC Mini): its emulation MUST be set to GS-232 — an ERC left on Hy-Gain DCU-1 speaks a different command set and will not answer — then pick its COM port and the same baud rate as in the ERC configuration.", 'rot.dcu1Hint': "Speaks the Hy-Gain DCU-1 command set (RotorCard DXA, Idiom Press Rotor-EZ, Green Heron). Connect over the controller's COM port (a DCU-1 is 4800 baud; RotorCard/Green Heron may differ — match the controller) or over TCP through a serial-over-IP bridge. Azimuth only, no elevation. New backend — please report if your controller needs a different command or baud.", 'rot.hint': "OpsLog sends UDP commands to PstRotator. Enable PstRotator's UDP listener (Setup → Communication → UDP) before testing.",
|
||||||
'extsvc.hint': 'Upload logged QSOs to online logbooks. Each service uploads automatically on a new QSO when enabled; timing is per-service (immediate, or a 1–2 min delay so a mis-logged QSO can still be fixed first).',
|
'extsvc.hint': 'Upload logged QSOs to online logbooks. Each service uploads automatically on a new QSO when enabled; timing is per-service (immediate, or a 1–2 min delay so a mis-logged QSO can still be fixed first).',
|
||||||
'hw.motorTxInhibit': 'Inhibit transmission while the antenna is moving', 'hw.motorTxInhibitHint': 'Blocks the FlexRadio from transmitting while the elements move (needs FlexRadio in API mode + this antenna enabled). No effect with other radios.', 'hw.motorAntenna': 'Ultrabeam / Steppir', 'hw.motorEnable': 'Enable antenna control', 'hw.motorType': 'Antenna type', 'hw.motorTransport': 'Connection', 'hw.motorTcp': 'Network (TCP)', 'hw.motorSerial': 'Serial (COM)', 'hw.motorCom': 'Serial port', 'hw.motorBaud': 'Baud', 'hw.steppirHint': 'SteppIR controllers are RS-232 serial (the DATA OUT DB9 port). Serial = a USB↔RS-232 (FTDI) adapter, shown as a COM port. Network = a serial-to-Ethernet bridge (as for the Ultrabeam).', 'hw.steppirRange': 'Tunable range', 'hw.steppirRangeHint': "The SteppIR's frequency coverage. On a band outside this range (e.g. 30 m on a 20 m–6 m SteppIR) OpsLog won't try to tune the antenna and won't inhibit transmission. Default 13–54 MHz (20 m–6 m); widen the low edge (e.g. 6) for a 40 m-equipped SteppIR.", 'hw.ultrabeam': 'Antenna (Ultrabeam)', 'hw.audioVoice': 'Audio devices & voice keyer',
|
'hw.motorTxInhibit': 'Inhibit transmission while the antenna is moving', 'hw.motorTxInhibitHint': 'Blocks the FlexRadio from transmitting while the elements move (needs FlexRadio in API mode + this antenna enabled). No effect with other radios.', 'hw.motorAntenna': 'Ultrabeam / Steppir', 'hw.motorEnable': 'Enable antenna control', 'hw.motorType': 'Antenna type', 'hw.motorTransport': 'Connection', 'hw.motorTcp': 'Network (TCP)', 'hw.motorSerial': 'Serial (COM)', 'hw.motorCom': 'Serial port', 'hw.motorBaud': 'Baud', 'hw.steppirHint': 'SteppIR controllers are RS-232 serial (the DATA OUT DB9 port). Serial = a USB↔RS-232 (FTDI) adapter, shown as a COM port. Network = a serial-to-Ethernet bridge (as for the Ultrabeam).', 'hw.steppirRange': 'Tunable range', 'hw.steppirRangeHint': "The SteppIR's frequency coverage. On a band outside this range (e.g. 30 m on a 20 m–6 m SteppIR) OpsLog won't try to tune the antenna and won't inhibit transmission. Default 13–54 MHz (20 m–6 m); widen the low edge (e.g. 6) for a 40 m-equipped SteppIR.", 'hw.ultrabeam': 'Antenna (Ultrabeam)', 'hw.audioVoice': 'Audio devices & voice keyer',
|
||||||
// CAT panel body
|
// CAT panel body
|
||||||
@@ -685,7 +685,7 @@ const fr: Dict = {
|
|||||||
'cat.hint': "Lit la fréquence / bande / mode du poste et les injecte dans le bandeau de saisie en temps réel. Utilise OmniRig (gratuit, tout poste) ou — pour FlexRadio — l'API native SmartSDR (sans OmniRig, temps réel, sans le bug du mode au 2ᵉ clic).",
|
'cat.hint': "Lit la fréquence / bande / mode du poste et les injecte dans le bandeau de saisie en temps réel. Utilise OmniRig (gratuit, tout poste) ou — pour FlexRadio — l'API native SmartSDR (sans OmniRig, temps réel, sans le bug du mode au 2ᵉ clic).",
|
||||||
'ag2.hint': "OpsLog dialogue avec le switch 4O3A Antenna Genius en TCP (protocole GSCP). Le port est fixé à 9007, seule l'IP de l'appareil est nécessaire. Un widget ancré permet ensuite de commuter les antennes par port (A/B).", 'ag2.password': 'Mot de passe distant', 'ag2.passwordPh': 'vide en LAN', 'ag2.passwordHint': "Nécessaire seulement à distance — l'appareil annonce alors « AG AUTH » et refuse les commandes tant qu'on n'est pas identifié. Laisse vide sur le réseau local.",
|
'ag2.hint': "OpsLog dialogue avec le switch 4O3A Antenna Genius en TCP (protocole GSCP). Le port est fixé à 9007, seule l'IP de l'appareil est nécessaire. Un widget ancré permet ensuite de commuter les antennes par port (A/B).", 'ag2.password': 'Mot de passe distant', 'ag2.passwordPh': 'vide en LAN', 'ag2.passwordHint': "Nécessaire seulement à distance — l'appareil annonce alors « AG AUTH » et refuse les commandes tant qu'on n'est pas identifié. Laisse vide sur le réseau local.",
|
||||||
'tg2.hint': "OpsLog dialogue avec le 4O3A Tuner Genius XL en TCP (port fixé à 9010), seule l'IP de l'appareil est nécessaire. Un widget ancré affiche le ROS et la puissance directe et propose Accord, Bypass et Operate/Standby. Pilotage direct (pas via la radio) pour n'utiliser qu'une des connexions de la boîte.", 'tg2.enable': "Activer le contrôle du Tuner Genius", 'tg2.portHint': "Le port TCP est fixé à 9010 sur l'appareil.", 'tg2.password': 'Code distant', 'tg2.passwordPh': 'vide en LAN', 'tg2.passwordHint': "Nécessaire seulement à distance — l'appareil annonce alors « AUTH » et refuse les commandes tant qu'on n'est pas identifié. Laisse vide sur le réseau local.",
|
'tg2.hint': "OpsLog dialogue avec le 4O3A Tuner Genius XL en TCP (port fixé à 9010), seule l'IP de l'appareil est nécessaire. Un widget ancré affiche le ROS et la puissance directe et propose Accord, Bypass et Operate/Standby. Pilotage direct (pas via la radio) pour n'utiliser qu'une des connexions de la boîte.", 'tg2.enable': "Activer le contrôle du Tuner Genius", 'tg2.portHint': "Le port TCP est fixé à 9010 sur l'appareil.", 'tg2.password': 'Code distant', 'tg2.passwordPh': 'vide en LAN', 'tg2.passwordHint': "Nécessaire seulement à distance — l'appareil annonce alors « AUTH » et refuse les commandes tant qu'on n'est pas identifié. Laisse vide sur le réseau local.",
|
||||||
'rot.enable': 'Activer le contrôle du rotator', 'rot.testOkRG': 'Connecté — le Rotator Genius a accepté la commande (rotation vers 0°).', 'rot.type': 'Type de rotator', 'rot.rotatorNum': 'Rotator n°', 'rot.dual': 'Deux rotors', 'rot.rotor1': 'Rotor 1', 'rot.rotor2': 'Rotor 2', 'rot.name': 'Nom', 'rot.antenna': 'Antenne', 'rot.antenna1': 'Antenne rotor 1', 'rot.antenna2': 'Antenne rotor 2', 'rot.name1': 'Nom du rotor 1', 'rot.name2': 'Nom du rotor 2', 'rot.antStd': 'Antenne standard', 'rot.antMotor': 'Motorisée (Ultrabeam/SteppIR)', 'rot.add': 'Ajouter un rotor', 'rot.remove': 'Supprimer le rotor', 'rot.none': 'Aucun rotor configuré. Cliquez sur « Ajouter un rotor ».', 'rot.rgDual': 'Ce Rotator Genius pilote deux rotors (en ajoute un second)', 'rot.testRotor1': 'Tester rotor 1', 'rot.testRotor2': 'Tester rotor 2', 'rot.dualHint': 'Deux rotors indépendants de n\'importe quel type — par exemple un ARCO et un Rotator Genius côte à côte. Configurez chacun ci-dessous ; OpsLog affiche un sélecteur Rotor 1/2 sur le compas pour choisir celui qu\'il tourne et affiche. Réglez chaque rotor sur « Motorisée » pour que les tracés de boom/diagramme (inverse, bidirectionnel) n\'apparaissent que pour celui qui porte l\'Ultrabeam/SteppIR. (Pour un seul Rotator Genius pilotant deux rotors, réglez les deux sur Rotator Genius avec le même hôte et le rotator n° 1 et 2.)', 'rot.rgHint': 'Parle directement à un Rotator Genius 4O3A en TCP (port 9006 par défaut) — sans PstRotator. Le n° choisit lequel des deux rotators du boîtier piloter.', 'rot.arcoHint': "Parle directement à tout contrôleur réglé sur Yaesu GS-232A — sans PstRotator. microHAM ARCO : régler Config → LAN → CONTROL PROTOCOL (ou USB CONTROL PROTOCOL) sur « Yaesu GS-232A » ; en USB la vitesse est sans importance. ERC (Easy Rotor Control, ERC Mini compris) : son émulation DOIT être réglée sur GS-232 — un ERC laissé en Hy-Gain DCU-1 parle un autre jeu de commandes et ne répondra pas — puis choisir son port COM et la même vitesse que dans la configuration de l'ERC.", 'rot.hint': "OpsLog envoie des commandes UDP à PstRotator. Active l'écouteur UDP de PstRotator (Setup → Communication → UDP) avant de tester.",
|
'rot.enable': 'Activer le contrôle du rotator', 'rot.testOkRG': 'Connecté — le Rotator Genius a accepté la commande (rotation vers 0°).', 'rot.type': 'Type de rotator', 'rot.rotatorNum': 'Rotator n°', 'rot.dual': 'Deux rotors', 'rot.rotor1': 'Rotor 1', 'rot.rotor2': 'Rotor 2', 'rot.name': 'Nom', 'rot.antenna': 'Antenne', 'rot.antenna1': 'Antenne rotor 1', 'rot.antenna2': 'Antenne rotor 2', 'rot.name1': 'Nom du rotor 1', 'rot.name2': 'Nom du rotor 2', 'rot.antStd': 'Antenne standard', 'rot.antMotor': 'Motorisée (Ultrabeam/SteppIR)', 'rot.add': 'Ajouter un rotor', 'rot.remove': 'Supprimer le rotor', 'rot.none': 'Aucun rotor configuré. Cliquez sur « Ajouter un rotor ».', 'rot.rgDual': 'Ce Rotator Genius pilote deux rotors (en ajoute un second)', 'rot.testRotor1': 'Tester rotor 1', 'rot.testRotor2': 'Tester rotor 2', 'rot.dualHint': 'Deux rotors indépendants de n\'importe quel type — par exemple un ARCO et un Rotator Genius côte à côte. Configurez chacun ci-dessous ; OpsLog affiche un sélecteur Rotor 1/2 sur le compas pour choisir celui qu\'il tourne et affiche. Réglez chaque rotor sur « Motorisée » pour que les tracés de boom/diagramme (inverse, bidirectionnel) n\'apparaissent que pour celui qui porte l\'Ultrabeam/SteppIR. (Pour un seul Rotator Genius pilotant deux rotors, réglez les deux sur Rotator Genius avec le même hôte et le rotator n° 1 et 2.)', 'rot.rgHint': 'Parle directement à un Rotator Genius 4O3A en TCP (port 9006 par défaut) — sans PstRotator. Le n° choisit lequel des deux rotators du boîtier piloter.', 'rot.arcoHint': "Parle directement à tout contrôleur réglé sur Yaesu GS-232A — sans PstRotator. microHAM ARCO : régler Config → LAN → CONTROL PROTOCOL (ou USB CONTROL PROTOCOL) sur « Yaesu GS-232A » ; en USB la vitesse est sans importance. ERC (Easy Rotor Control, ERC Mini compris) : son émulation DOIT être réglée sur GS-232 — un ERC laissé en Hy-Gain DCU-1 parle un autre jeu de commandes et ne répondra pas — puis choisir son port COM et la même vitesse que dans la configuration de l'ERC.", 'rot.dcu1Hint': "Parle le jeu de commandes Hy-Gain DCU-1 (RotorCard DXA, Idiom Press Rotor-EZ, Green Heron). Connexion via le port COM du contrôleur (un DCU-1 est à 4800 bauds ; RotorCard/Green Heron peuvent différer — reprendre la vitesse du contrôleur) ou en TCP via un pont série-sur-IP. Azimut uniquement, pas d'élévation. Nouveau pilote — signalez si votre contrôleur nécessite une autre commande ou vitesse.", 'rot.hint': "OpsLog envoie des commandes UDP à PstRotator. Active l'écouteur UDP de PstRotator (Setup → Communication → UDP) avant de tester.",
|
||||||
'extsvc.hint': "Envoie les QSO enregistrés vers des carnets en ligne. Chaque service upload automatiquement à chaque nouveau QSO si activé ; le délai est propre à chaque service (immédiat, ou 1–2 min pour corriger un QSO mal saisi avant).",
|
'extsvc.hint': "Envoie les QSO enregistrés vers des carnets en ligne. Chaque service upload automatiquement à chaque nouveau QSO si activé ; le délai est propre à chaque service (immédiat, ou 1–2 min pour corriger un QSO mal saisi avant).",
|
||||||
'hw.motorTxInhibit': "Inhiber la transmission pendant que l'antenne bouge", 'hw.motorTxInhibitHint': "Empêche le FlexRadio d'émettre pendant que les éléments bougent (nécessite le FlexRadio en API + cette antenne activée). Sans effet avec les autres radios.", 'hw.motorAntenna': 'Antenne motorisée', 'hw.motorEnable': "Activer le contrôle de l'antenne", 'hw.motorType': "Type d'antenne", 'hw.motorTransport': 'Connexion', 'hw.motorTcp': 'Réseau (TCP)', 'hw.motorSerial': 'Série (COM)', 'hw.motorCom': 'Port série', 'hw.motorBaud': 'Débit', 'hw.steppirHint': "Les contrôleurs SteppIR sont en RS-232 série (port DB9 « DATA OUT »). Série = un adaptateur USB↔RS-232 (FTDI), vu comme un port COM. Réseau = un pont série-Ethernet (comme pour l'Ultrabeam).", 'hw.steppirRange': 'Plage accordable', 'hw.steppirRangeHint': "La couverture en fréquence de la SteppIR. Sur une bande hors de cette plage (p. ex. 30 m avec une SteppIR 20 m-6 m), OpsLog n'essaie pas d'accorder l'antenne et n'inhibe pas l'émission. Défaut 13-54 MHz (20 m-6 m) ; abaisse la borne basse (p. ex. 6) pour une SteppIR équipée 40 m.", 'hw.ultrabeam': 'Antenne (Ultrabeam)', 'hw.audioVoice': 'Périphériques audio & manipulateur vocal',
|
'hw.motorTxInhibit': "Inhiber la transmission pendant que l'antenne bouge", 'hw.motorTxInhibitHint': "Empêche le FlexRadio d'émettre pendant que les éléments bougent (nécessite le FlexRadio en API + cette antenne activée). Sans effet avec les autres radios.", 'hw.motorAntenna': 'Antenne motorisée', 'hw.motorEnable': "Activer le contrôle de l'antenne", 'hw.motorType': "Type d'antenne", 'hw.motorTransport': 'Connexion', 'hw.motorTcp': 'Réseau (TCP)', 'hw.motorSerial': 'Série (COM)', 'hw.motorCom': 'Port série', 'hw.motorBaud': 'Débit', 'hw.steppirHint': "Les contrôleurs SteppIR sont en RS-232 série (port DB9 « DATA OUT »). Série = un adaptateur USB↔RS-232 (FTDI), vu comme un port COM. Réseau = un pont série-Ethernet (comme pour l'Ultrabeam).", 'hw.steppirRange': 'Plage accordable', 'hw.steppirRangeHint': "La couverture en fréquence de la SteppIR. Sur une bande hors de cette plage (p. ex. 30 m avec une SteppIR 20 m-6 m), OpsLog n'essaie pas d'accorder l'antenne et n'inhibe pas l'émission. Défaut 13-54 MHz (20 m-6 m) ; abaisse la borne basse (p. ex. 6) pour une SteppIR équipée 40 m.", 'hw.ultrabeam': 'Antenne (Ultrabeam)', 'hw.audioVoice': 'Périphériques audio & manipulateur vocal',
|
||||||
'cat.enable': 'Activer le CAT', 'cat.backend': 'Backend', 'cat.optOmnirig': 'OmniRig', 'cat.optFlex': 'FlexRadio (API)', 'cat.share': 'Partager le CAT avec les autres logiciels', 'cat.shareHint': "Dans l'autre logiciel, choisissez le modèle « Hamlib NET rigctl » et saisissez 127.0.0.1:4532. Fonctionne avec tous les backends, pas seulement les natifs.", 'cat.sharePort': 'Port de partage', 'cat.pttKey': 'Activer la touche PTT', 'cat.pttKeyPress': 'Appuyez sur une touche…', 'cat.pttKeyNone': 'Cliquez pour définir une touche', 'cat.pttKeyClear': 'Effacer', 'cat.pttKeyToggle': 'Mode bascule (appui = émission, nouvel appui = arrêt)', 'cat.pttKeyHint': "Quand OpsLog a le focus, cette touche passe la radio en émission — maintenue par défaut (relâcher pour arrêter), ou verrouillée en mode bascule. Elle utilise la méthode PTT de Audio → PTT (CAT / RTS / DTR), avec repli sur le CAT. Choisissez une touche que vous ne tapez jamais en journalisant (ex. Pause, Arrêt défil., ou une pédale mappée dessus) — OpsLog l'intercepte pour qu'elle n'atterrisse pas dans un champ.", 'cat.optXiegu': 'Xiegu (USB)', 'cat.xieguPort': 'Port CAT Xiegu', 'cat.xieguBaudHint': 'Doit correspondre au menu de la radio (G90 par défaut : 19200).', 'cat.xieguAddrHint': "Adresse CI-V d'usine de la famille G90/X6100 : 0x70.", 'cat.xieguPTTLine': 'Passage en \u00e9mission', 'cat.xieguPTTCiv': 'Commande CI-V', 'cat.xieguPTTHint': 'Un G90 ne passe pas en \u00e9mission sur la commande CI-V : les interfaces comme le DE-19 le pilotent par RTS ou DTR. Choisissez la ligne de la v\u00f4tre \u2014 c\u2019est aussi ce qui permet \u00e0 WSJT-X d\u2019\u00e9mettre via le partage CAT.', 'cat.optYaesu': 'Yaesu (USB)', 'cat.optKenwood': 'Kenwood (USB, réseau)', 'cat.civTrace': 'Journaliser le protocole CAT', 'cat.civTraceHint': '\u00c9crit dans le journal chaque trame CAT \u00e9chang\u00e9e avec la radio \u2014 CI-V en hexad\u00e9cimal, Kenwood en texte. Pour signaler une radio qui r\u00e9pond de travers \u2014 un bouton qui fait autre chose, une fr\u00e9quence qui saute. Valable pour la session seulement : c\u2019est un diagnostic, et le journal grossit vite.', 'cat.kenwoodPort': 'Port COM Kenwood', 'cat.kenwoodPortHint': 'Le port s\u00e9rie CAT/USB de la radio (TS-590, TS-890, TS-990, TS-2000, ainsi que les Elecraft K3/K4 qui parlent le m\u00eame dialecte).', 'cat.kenwoodBaudHint': 'Doit correspondre au MENU de la radio : un TS-590 sort d\u2019usine \u00e0 9600, un TS-890 \u00e0 115200.', 'cat.kenwoodHost': 'Ou par le r\u00e9seau (h\u00f4te:port)', 'cat.kenwoodHostHint': 'Un pont s\u00e9rie-r\u00e9seau \u2014 ser2net, un bo\u00eetier Ethernet-s\u00e9rie, un Raspberry Pi pr\u00e8s de la radio. S\u2019il est rempli, il est utilis\u00e9 \u00c0 LA PLACE du port COM ci-dessus. Il ne s\u2019agit pas de la prise RJ45 de la radio, qui parle le protocole KNS de Kenwood et n\u2019est pas prise en charge.', 'cat.yaesuPort': 'Port CAT Yaesu', 'cat.yaesuPortHint': "Le port CAT de la radio — sur un FTDX10/FTDX101 en USB c'est le port COM ENHANCED, pas le standard.", 'cat.yaesuBaudHint': 'Doit correspondre au menu de la radio (FTDX10/FTDX101 par défaut : 38400).', 'cat.lowerLines': 'Abaisser les lignes DTR et RTS à la connexion', 'cat.lowerLinesHint': 'Si votre radio est toujours en émission, cochez ceci.', 'cat.kwDataMode': 'Modes data (FT8/PSK…)', 'cat.kwDataUsb': 'USB', 'cat.kwDataMd6': 'Mode DATA — MD6 (Elecraft K3/K4)', 'cat.kwDataKeep': 'Ne pas changer le mode de la radio', 'cat.kwDataHint': "Ce qu'OpsLog règle sur la radio pour un mode data. Aucune commande unique ne convient à toutes : un Elecraft K3/K4 veut DATA (MD6) ; sur un TS-590SG/TS-990S le mode data est un modificateur d'USB réglé sur la radio, choisissez donc USB ou, plus sûr, « Ne pas changer » et passez la radio en DATA vous-même. En MD6 un Kenwood classique (TS-590/990) tomberait en FSK/RTTY — à ne pas utiliser là.", 'cat.optIcom': 'Icom (CI-V USB)', 'cat.optIcomNet': 'Icom (CI-V réseau)', 'cat.optTci': 'TCI', 'flxpw.title': 'FlexRadio \u2014 puissance par bande et par mode', 'flxpw.hint': 'Puissance d\u2019\u00e9mission appliqu\u00e9e au changement de bande ou de mode. Laissez une case vide pour ne pas toucher \u00e0 la puissance.', 'flxpw.band': 'Bande', 'flxb.title': 'FlexRadio \u2014 antennes et puissance par bande', 'flxb.hint': 'Les antennes sont appliqu\u00e9es au changement de bande ; la puissance au changement de bande ou de mode. Laissez une case de puissance vide pour ne pas y toucher.', 'flxb.rxAnt': 'Antenne RX', 'flxb.txAnt': 'Antenne TX', 'flxb.noRadio': 'Aucune antenne remont\u00e9e \u2014 connectez le FlexRadio, puis rouvrez ce panneau.', 'flxpw.phone': 'Phonie', 'flxpw.digi': 'Num\u00e9rique', 'flxpw.noBands': 'Aucune bande configur\u00e9e \u2014 ajoutez-les dans Listes \u2192 Bandes.', 'flxpw.saved': 'Enregistr\u00e9',
|
'cat.enable': 'Activer le CAT', 'cat.backend': 'Backend', 'cat.optOmnirig': 'OmniRig', 'cat.optFlex': 'FlexRadio (API)', 'cat.share': 'Partager le CAT avec les autres logiciels', 'cat.shareHint': "Dans l'autre logiciel, choisissez le modèle « Hamlib NET rigctl » et saisissez 127.0.0.1:4532. Fonctionne avec tous les backends, pas seulement les natifs.", 'cat.sharePort': 'Port de partage', 'cat.pttKey': 'Activer la touche PTT', 'cat.pttKeyPress': 'Appuyez sur une touche…', 'cat.pttKeyNone': 'Cliquez pour définir une touche', 'cat.pttKeyClear': 'Effacer', 'cat.pttKeyToggle': 'Mode bascule (appui = émission, nouvel appui = arrêt)', 'cat.pttKeyHint': "Quand OpsLog a le focus, cette touche passe la radio en émission — maintenue par défaut (relâcher pour arrêter), ou verrouillée en mode bascule. Elle utilise la méthode PTT de Audio → PTT (CAT / RTS / DTR), avec repli sur le CAT. Choisissez une touche que vous ne tapez jamais en journalisant (ex. Pause, Arrêt défil., ou une pédale mappée dessus) — OpsLog l'intercepte pour qu'elle n'atterrisse pas dans un champ.", 'cat.optXiegu': 'Xiegu (USB)', 'cat.xieguPort': 'Port CAT Xiegu', 'cat.xieguBaudHint': 'Doit correspondre au menu de la radio (G90 par défaut : 19200).', 'cat.xieguAddrHint': "Adresse CI-V d'usine de la famille G90/X6100 : 0x70.", 'cat.xieguPTTLine': 'Passage en \u00e9mission', 'cat.xieguPTTCiv': 'Commande CI-V', 'cat.xieguPTTHint': 'Un G90 ne passe pas en \u00e9mission sur la commande CI-V : les interfaces comme le DE-19 le pilotent par RTS ou DTR. Choisissez la ligne de la v\u00f4tre \u2014 c\u2019est aussi ce qui permet \u00e0 WSJT-X d\u2019\u00e9mettre via le partage CAT.', 'cat.optYaesu': 'Yaesu (USB)', 'cat.optKenwood': 'Kenwood (USB, réseau)', 'cat.civTrace': 'Journaliser le protocole CAT', 'cat.civTraceHint': '\u00c9crit dans le journal chaque trame CAT \u00e9chang\u00e9e avec la radio \u2014 CI-V en hexad\u00e9cimal, Kenwood en texte. Pour signaler une radio qui r\u00e9pond de travers \u2014 un bouton qui fait autre chose, une fr\u00e9quence qui saute. Valable pour la session seulement : c\u2019est un diagnostic, et le journal grossit vite.', 'cat.kenwoodPort': 'Port COM Kenwood', 'cat.kenwoodPortHint': 'Le port s\u00e9rie CAT/USB de la radio (TS-590, TS-890, TS-990, TS-2000, ainsi que les Elecraft K3/K4 qui parlent le m\u00eame dialecte).', 'cat.kenwoodBaudHint': 'Doit correspondre au MENU de la radio : un TS-590 sort d\u2019usine \u00e0 9600, un TS-890 \u00e0 115200.', 'cat.kenwoodHost': 'Ou par le r\u00e9seau (h\u00f4te:port)', 'cat.kenwoodHostHint': 'Un pont s\u00e9rie-r\u00e9seau \u2014 ser2net, un bo\u00eetier Ethernet-s\u00e9rie, un Raspberry Pi pr\u00e8s de la radio. S\u2019il est rempli, il est utilis\u00e9 \u00c0 LA PLACE du port COM ci-dessus. Il ne s\u2019agit pas de la prise RJ45 de la radio, qui parle le protocole KNS de Kenwood et n\u2019est pas prise en charge.', 'cat.yaesuPort': 'Port CAT Yaesu', 'cat.yaesuPortHint': "Le port CAT de la radio — sur un FTDX10/FTDX101 en USB c'est le port COM ENHANCED, pas le standard.", 'cat.yaesuBaudHint': 'Doit correspondre au menu de la radio (FTDX10/FTDX101 par défaut : 38400).', 'cat.lowerLines': 'Abaisser les lignes DTR et RTS à la connexion', 'cat.lowerLinesHint': 'Si votre radio est toujours en émission, cochez ceci.', 'cat.kwDataMode': 'Modes data (FT8/PSK…)', 'cat.kwDataUsb': 'USB', 'cat.kwDataMd6': 'Mode DATA — MD6 (Elecraft K3/K4)', 'cat.kwDataKeep': 'Ne pas changer le mode de la radio', 'cat.kwDataHint': "Ce qu'OpsLog règle sur la radio pour un mode data. Aucune commande unique ne convient à toutes : un Elecraft K3/K4 veut DATA (MD6) ; sur un TS-590SG/TS-990S le mode data est un modificateur d'USB réglé sur la radio, choisissez donc USB ou, plus sûr, « Ne pas changer » et passez la radio en DATA vous-même. En MD6 un Kenwood classique (TS-590/990) tomberait en FSK/RTTY — à ne pas utiliser là.", 'cat.optIcom': 'Icom (CI-V USB)', 'cat.optIcomNet': 'Icom (CI-V réseau)', 'cat.optTci': 'TCI', 'flxpw.title': 'FlexRadio \u2014 puissance par bande et par mode', 'flxpw.hint': 'Puissance d\u2019\u00e9mission appliqu\u00e9e au changement de bande ou de mode. Laissez une case vide pour ne pas toucher \u00e0 la puissance.', 'flxpw.band': 'Bande', 'flxb.title': 'FlexRadio \u2014 antennes et puissance par bande', 'flxb.hint': 'Les antennes sont appliqu\u00e9es au changement de bande ; la puissance au changement de bande ou de mode. Laissez une case de puissance vide pour ne pas y toucher.', 'flxb.rxAnt': 'Antenne RX', 'flxb.txAnt': 'Antenne TX', 'flxb.noRadio': 'Aucune antenne remont\u00e9e \u2014 connectez le FlexRadio, puis rouvrez ce panneau.', 'flxpw.phone': 'Phonie', 'flxpw.digi': 'Num\u00e9rique', 'flxpw.noBands': 'Aucune bande configur\u00e9e \u2014 ajoutez-les dans Listes \u2192 Bandes.', 'flxpw.saved': 'Enregistr\u00e9',
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// Package dcu1 drives rotator controllers that speak the Hy-Gain DCU-1 protocol,
|
||||||
|
// over a serial COM port (or a raw TCP socket, e.g. a serial-over-IP bridge).
|
||||||
|
//
|
||||||
|
// DCU-1 is used by the Hy-Gain DCU-1, the Idiom Press Rotor-EZ, Green Heron
|
||||||
|
// controllers, and the RotorCard DXA (hamsupply) for Yaesu DXA rotors. It is a
|
||||||
|
// DIFFERENT command set from Yaesu GS-232 (see internal/rotator/gs232):
|
||||||
|
// semicolon-terminated, azimuth only.
|
||||||
|
//
|
||||||
|
// Commands (';' terminated — roundTrip appends the ';'):
|
||||||
|
//
|
||||||
|
// AP1nnn set the target bearing nnn (000-359)
|
||||||
|
// AM1 rotate to the target (some controllers move on AP1 alone; AM1 is
|
||||||
|
// harmless and makes the Rotor-EZ/DCU-1 variants that need it work)
|
||||||
|
// AI1 query the current bearing → the reply carries the 3-digit azimuth
|
||||||
|
//
|
||||||
|
// The base DCU-1 set has no dedicated stop; Stop re-commands the current bearing,
|
||||||
|
// which halts rotation.
|
||||||
|
package dcu1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.bug.st/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dialTimeout = 3 * time.Second
|
||||||
|
ioTimeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a stateless per-call sender, mirroring the gs232/pst/rotgenius idiom.
|
||||||
|
// Exactly one of (Host, Port) or ComPort is used.
|
||||||
|
type Client struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
ComPort string // serial transport: "COM5" etc.
|
||||||
|
// Baud varies by controller (a Hy-Gain DCU-1 is 4800; Green Heron / RotorCard
|
||||||
|
// can differ). Zero keeps 4800.
|
||||||
|
Baud int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a TCP Client (a serial-over-IP bridge in front of the controller).
|
||||||
|
func New(host string, port int) *Client {
|
||||||
|
if host == "" {
|
||||||
|
host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
if port <= 0 || port > 65535 {
|
||||||
|
port = 4001
|
||||||
|
}
|
||||||
|
return &Client{Host: host, Port: port}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSerial returns a Client over the controller's COM port.
|
||||||
|
func NewSerial(comPort string, baud int) *Client {
|
||||||
|
return &Client{ComPort: comPort, Baud: baud}
|
||||||
|
}
|
||||||
|
|
||||||
|
// roundTrip opens a connection, sends one ';'-terminated command and (when
|
||||||
|
// wantReply) reads until a 3-digit bearing is present. cmd must NOT carry the
|
||||||
|
// ';'.
|
||||||
|
func (c *Client) roundTrip(cmd string, wantReply bool) (string, error) {
|
||||||
|
var conn io.ReadWriteCloser
|
||||||
|
if c.ComPort != "" {
|
||||||
|
baud := c.Baud
|
||||||
|
if baud <= 0 {
|
||||||
|
baud = 4800
|
||||||
|
}
|
||||||
|
sp, err := serial.Open(c.ComPort, &serial.Mode{BaudRate: baud})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("open rotator %s @ %d baud: %w", c.ComPort, baud, err)
|
||||||
|
}
|
||||||
|
_ = sp.SetReadTimeout(200 * time.Millisecond)
|
||||||
|
conn = sp
|
||||||
|
} else {
|
||||||
|
nc, err := net.DialTimeout("tcp", net.JoinHostPort(c.Host, strconv.Itoa(c.Port)), dialTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("connect DCU-1 %s:%d: %w", c.Host, c.Port, err)
|
||||||
|
}
|
||||||
|
_ = nc.SetDeadline(time.Now().Add(ioTimeout))
|
||||||
|
conn = nc
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if _, err := conn.Write([]byte(cmd + ";")); err != nil {
|
||||||
|
return "", fmt.Errorf("send %q: %w", cmd, err)
|
||||||
|
}
|
||||||
|
if !wantReply {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, 64)
|
||||||
|
var sb strings.Builder
|
||||||
|
deadline := time.Now().Add(ioTimeout)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
sb.Write(buf[:n])
|
||||||
|
// The DCU-1 reply carries the bearing as three digits (framing varies —
|
||||||
|
// ";nnn", "nnn;", "+0nnn"). Stop once we have them rather than on a
|
||||||
|
// specific terminator, so any flavour reads cleanly.
|
||||||
|
if azRe.MatchString(sb.String()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A serial read that times out returns (0, nil) — keep polling until the
|
||||||
|
// overall deadline; a real error ends the read.
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line := strings.TrimSpace(sb.String())
|
||||||
|
if line == "" {
|
||||||
|
return "", fmt.Errorf("no reply to %q", cmd)
|
||||||
|
}
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoTo points the antenna at az (0-359): set the target, then rotate.
|
||||||
|
func (c *Client) GoTo(az int) error {
|
||||||
|
az = ((az % 360) + 360) % 360
|
||||||
|
if _, err := c.roundTrip(fmt.Sprintf("AP1%03d", az), false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := c.roundTrip("AM1", false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop halts rotation. The base DCU-1 set has no stop command, so re-command the
|
||||||
|
// current bearing — the controller stops when the target equals where it is.
|
||||||
|
func (c *Client) Stop() error {
|
||||||
|
az, _, err := c.Heading()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.roundTrip(fmt.Sprintf("AP1%03d", az), false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// azRe matches the 3-digit bearing in any of the DCU-1 reply framings.
|
||||||
|
var azRe = regexp.MustCompile(`(\d{3})`)
|
||||||
|
|
||||||
|
// Heading queries the current azimuth. Returns the raw reply for diagnostics.
|
||||||
|
func (c *Client) Heading() (az int, raw string, err error) {
|
||||||
|
raw, err = c.roundTrip("AI1", true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, raw, err
|
||||||
|
}
|
||||||
|
m := azRe.FindStringSubmatch(raw)
|
||||||
|
if m == nil {
|
||||||
|
return 0, raw, fmt.Errorf("unrecognised azimuth reply %q", raw)
|
||||||
|
}
|
||||||
|
az, _ = strconv.Atoi(m[1])
|
||||||
|
return az % 360, raw, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user