diff --git a/app.go b/app.go index bad8056..589fc6e 100644 --- a/app.go +++ b/app.go @@ -178,7 +178,8 @@ const ( keyRotatorType = "rotator.type" // "pst" (PstRotator UDP) | "rotgenius" (4O3A native TCP) | "arco" (microHAM ARCO, GS-232A) keyRotatorNum = "rotator.num" // Rotator Genius rotator index: 1 or 2 keyRotatorTransport = "rotator.transport" // arco: "tcp" (LAN) | "serial" (USB virtual COM) - keyRotatorComPort = "rotator.com_port" // arco serial transport + keyRotatorComPort = "rotator.com_port" // GS-232 serial transport + keyRotatorBaud = "rotator.baud" // GS-232 serial baud (an ERC needs it; an ARCO ignores it) // Motorized antenna (Ultrabeam or SteppIR) — Hardware → Antenna. Keys keep the // "ultrabeam." prefix for backward compatibility with configs saved before the @@ -12550,8 +12551,11 @@ type RotatorSettings struct { Port int `json:"port"` // default 12000 (pst) / 9006 (rotgenius) / 4001 (arco — must match the port set in ARCO's LAN CONTROL PROTOCOL) HasElevation bool `json:"has_elevation"` // include EL in GoTo packets (PstRotator) RotatorNum int `json:"rotator_num"` // Rotator Genius index: 1 or 2 - Transport string `json:"transport"` // arco: "tcp" (LAN) | "serial" (USB virtual COM) - ComPort string `json:"com_port"` // arco serial transport + Transport string `json:"transport"` // GS-232 controller: "tcp" (LAN) | "serial" (COM) + ComPort string `json:"com_port"` // GS-232 serial transport + // Baud for the serial transport. An ARCO's virtual COM ignores it; an ERC runs + // at the rate set in its own configuration, so it has to be selectable. + Baud int `json:"baud"` } // GetRotatorSettings returns the persisted rotator config with defaults. @@ -12562,7 +12566,7 @@ func (a *App) GetRotatorSettings() (RotatorSettings, error) { } m, err := a.settings.GetMany(a.ctx, keyRotatorEnabled, keyRotatorHost, keyRotatorPort, keyRotatorHasElevation, keyRotatorType, keyRotatorNum, - keyRotatorTransport, keyRotatorComPort) + keyRotatorTransport, keyRotatorComPort, keyRotatorBaud) if err != nil { return out, err } @@ -12589,6 +12593,10 @@ func (a *App) GetRotatorSettings() (RotatorSettings, error) { out.Transport = "serial" } out.ComPort = m[keyRotatorComPort] + out.Baud = 9600 + if b, _ := strconv.Atoi(m[keyRotatorBaud]); b > 0 { + out.Baud = b + } return out, nil } @@ -12622,6 +12630,7 @@ func (a *App) SaveRotatorSettings(s RotatorSettings) error { keyRotatorNum: strconv.Itoa(s.RotatorNum), keyRotatorTransport: s.Transport, keyRotatorComPort: strings.TrimSpace(s.ComPort), + keyRotatorBaud: strconv.Itoa(s.Baud), } { if err := a.settings.Set(a.ctx, k, v); err != nil { return err @@ -12634,7 +12643,7 @@ func (a *App) SaveRotatorSettings(s RotatorSettings) error { // USB virtual COM (serial) or the LAN CONTROL PROTOCOL TCP port. func arcoClient(s RotatorSettings) *gs232.Client { if s.Transport == "serial" { - return gs232.NewSerial(s.ComPort) + return gs232.NewSerial(s.ComPort, s.Baud) } return gs232.New(s.Host, s.Port) } diff --git a/changelog.json b/changelog.json index 65e49b2..fa51716 100644 --- a/changelog.json +++ b/changelog.json @@ -17,7 +17,8 @@ "Auto-call CW now waits the gap you set AFTER the message, not from the moment it starts. With a WinKeyer or serial keyer the wait could end before the call had finished, so a 6 s gap gave about 2 s of silence after a 4 s CQ.", "The Antenna Genius and Tuner Genius settings entries carry a small 4O3A mark, so the panels driving that hardware are recognisable at a glance in the sidebar.", "The database shown in the status bar is the LOGBOOK file. On a local SQLite setup it named the settings database instead, so anyone checking where their QSOs live — or which file to back up — was pointed at the wrong one.", - "Yaesu: the mode follows the VFO you are on. A spot clicked while working the SUB receiver tuned the sub VFO but set the mode on MAIN, leaving the VFO in use on its old mode; the console read main mode too." + "Yaesu: the mode follows the VFO you are on. A spot clicked while working the SUB receiver tuned the sub VFO but set the mode on MAIN, leaving the VFO in use on its old mode; the console read main mode too.", + "Rotator control now covers any GS-232A controller, ERC (Easy Rotor Control) included: the serial speed is selectable and the entry is named after the protocol rather than one device. Set the ERC to GS-232 emulation, not Hy-Gain DCU-1." ], "fr": [ "Cliquer sur un spot du cluster en écoutant sur le VFO SUB ne ramène plus la radio sur le VFO principal. La commande utilisée pour s'accorder agit par définition sur le VFO principal ; sur SUB, c'est désormais le VFO secondaire qui est écrit, sans toucher à la sélection.", @@ -34,7 +35,8 @@ "L'appel automatique en CW attend désormais l'intervalle réglé APRÈS le message, et non depuis son début. Avec un WinKeyer ou un keyer série, l'attente pouvait se terminer avant la fin de l'appel : un intervalle de 6 s ne laissait qu'environ 2 s de silence après un CQ de 4 s.", "Les entrées de réglages Antenna Genius et Tuner Genius portent une petite marque 4O3A : les panneaux pilotant ce matériel se repèrent d'un coup d'œil dans le menu.", "La base affichée dans la barre d'état est bien le fichier du CARNET. En SQLite local, elle désignait la base des réglages : qui vérifiait où résident ses QSO — ou quel fichier sauvegarder — était orienté vers le mauvais.", - "Yaesu : le mode suit le VFO utilisé. Un spot cliqué en travaillant sur le récepteur SUB accordait bien le VFO secondaire mais réglait le mode sur le MAIN, laissant le VFO en service sur son ancien mode ; la console lisait également le mode du principal." + "Yaesu : le mode suit le VFO utilisé. Un spot cliqué en travaillant sur le récepteur SUB accordait bien le VFO secondaire mais réglait le mode sur le MAIN, laissant le VFO en service sur son ancien mode ; la console lisait également le mode du principal.", + "Le contrôle de rotator couvre désormais tout contrôleur GS-232A, ERC (Easy Rotor Control) compris : la vitesse du port série est sélectionnable et l'entrée porte le nom du protocole plutôt que celui d'un seul appareil. Réglez l'ERC sur l'émulation GS-232, pas Hy-Gain DCU-1." ] }, { diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 68221f2..27c9359 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -3198,7 +3198,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan PstRotator (UDP) Rotator Genius (4O3A, native) - microHAM ARCO (LAN, GS-232A) + GS-232A controller (microHAM ARCO, ERC…) @@ -3245,6 +3245,15 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan + ) : ( diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx index 8e7dbbf..deaa887 100644 --- a/frontend/src/lib/i18n.tsx +++ b/frontend/src/lib/i18n.tsx @@ -273,7 +273,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).", '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.', - '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.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 a microHAM ARCO — no PstRotator needed. LAN: set Config → LAN → CONTROL PROTOCOL to 'Yaesu GS-232A' on the ARCO and enter the same TCP port here (up to 4 parallel connections). USB: set USB CONTROL PROTOCOL to 'Yaesu GS-232A' and pick the ARCO's COM port here (baud rate doesn't matter on USB).", '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.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.", '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', // CAT panel body @@ -675,7 +675,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).", '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.", - '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.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 à un microHAM ARCO — sans PstRotator. Réseau : régler Config → LAN → CONTROL PROTOCOL sur « Yaesu GS-232A » sur l'ARCO et saisir ici le même port TCP (jusqu'à 4 connexions en parallèle). USB : régler USB CONTROL PROTOCOL sur « Yaesu GS-232A » et choisir ici le port COM de l'ARCO (la vitesse est sans importance en USB).", '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.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.", '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', 'cat.enable': 'Activer le CAT', 'cat.backend': 'Backend', 'cat.optOmnirig': 'OmniRig (tout poste, COM Windows)', 'cat.optFlex': 'FlexRadio / SmartSDR (natif)', 'cat.share': 'Partager le CAT avec les autres logiciels', 'cat.shareHint': "Un backend CAT natif occupe le port série de la radio, aucun autre logiciel ne peut donc y accéder. Ceci permet à WSJT-X, JTDX, MSHV ou Log4OM de dialoguer avec la radio À TRAVERS OpsLog : 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.optXiegu': 'Xiegu (CI-V natif)', '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.optYaesu': 'Yaesu (CAT natif)', '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.optIcom': 'Icom CI-V (USB série)', 'cat.optIcomNet': 'Icom CI-V (réseau / remote)', 'cat.optTci': 'TCI (Expert Electronics / SunSDR)', diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index eb03f17..9e04b42 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -2799,6 +2799,7 @@ export namespace main { rotator_num: number; transport: string; com_port: string; + baud: number; static createFrom(source: any = {}) { return new RotatorSettings(source); @@ -2814,6 +2815,7 @@ export namespace main { this.rotator_num = source["rotator_num"]; this.transport = source["transport"]; this.com_port = source["com_port"]; + this.baud = source["baud"]; } } export class ScpStatus { diff --git a/internal/rotator/gs232/gs232.go b/internal/rotator/gs232/gs232.go index 3452369..d4f7a69 100644 --- a/internal/rotator/gs232/gs232.go +++ b/internal/rotator/gs232/gs232.go @@ -1,6 +1,12 @@ // Package gs232 drives rotator controllers that speak the Yaesu GS-232A -// protocol, over a raw TCP socket or a serial COM port. The target device is -// the microHAM ARCO: both its LAN "CONTROL PROTOCOL" setting (a TCP port) and +// protocol, over a raw TCP socket or a serial COM port. +// +// Any controller set to GS-232A works, which is most of them: the microHAM ARCO +// natively, and the ERC family (Easy Rotor Control — ERC Mini, ERC interface), +// which EMULATES GS-232A/B over its USB port. An ERC can also be configured for +// Hy-Gain DCU-1, a different command set entirely, so it must be set to GS-232. +// +// On the ARCO: both its LAN "CONTROL PROTOCOL" setting (a TCP port) and // its USB port ("USB CONTROL PROTOCOL", a virtual COM where the baud rate is // irrelevant) can be set to speak Yaesu GS-232A — so OpsLog controls it // directly, no PstRotator in between. ARCO accepts up to four parallel LAN @@ -37,7 +43,12 @@ const ( type Client struct { Host string Port int - ComPort string // serial transport: "COM5" etc. (ARCO USB virtual COM — any baud) + ComPort string // serial transport: "COM5" etc. + // Baud matters on some controllers. An ARCO's USB virtual COM ignores it, but + // an ERC (Easy Rotor Control) runs at whatever rate its own configuration + // sets — commonly 9600 or 19200 — and a mismatch reads as a dead rotator. + // Zero keeps the historical 9600. + Baud int } // New returns a TCP Client with sane defaults applied for empty fields. There @@ -56,8 +67,8 @@ func New(host string, port int) *Client { // NewSerial returns a Client talking over the ARCO's USB virtual COM port. The // baud rate is irrelevant on USB per the ARCO manual (8N1 framing matters); we // open at 9600 which also suits a real RS-232 hookup left at its default. -func NewSerial(comPort string) *Client { - return &Client{ComPort: comPort} +func NewSerial(comPort string, baud int) *Client { + return &Client{ComPort: comPort, Baud: baud} } // roundTrip opens a connection (TCP or serial per the client's config), sends @@ -66,9 +77,13 @@ func NewSerial(comPort string) *Client { func (c *Client) roundTrip(cmd string, wantReply bool) (string, error) { var conn io.ReadWriteCloser if c.ComPort != "" { - sp, err := serial.Open(c.ComPort, &serial.Mode{BaudRate: 9600}) + baud := c.Baud + if baud <= 0 { + baud = 9600 + } + sp, err := serial.Open(c.ComPort, &serial.Mode{BaudRate: baud}) if err != nil { - return "", fmt.Errorf("open ARCO %s: %w", c.ComPort, err) + return "", fmt.Errorf("open rotator %s @ %d baud: %w", c.ComPort, baud, err) } _ = sp.SetReadTimeout(200 * time.Millisecond) conn = sp