This commit is contained in:
2026-06-03 21:53:31 +02:00
parent 2b4326b553
commit 1a425a1b0d
15 changed files with 377 additions and 97 deletions
+30 -12
View File
@@ -6,20 +6,38 @@ import (
"path/filepath"
)
// debugLog writes CAT debug events to %APPDATA%/HamLog/cat.log so users can
// diagnose mode/freq mismatches without rebuilding with -windowsconsole.
//
// Initialised lazily on first use. Falls back to the standard library
// default logger (stderr, usually invisible in a Wails GUI build) if the
// log file can't be opened.
var debugLog = openDebugLog()
// LogSink, when set by the host app at startup, receives every CAT debug
// line so they land in the unified app log (opslog.log) alongside the rest
// of OpsLog's diagnostics. Until it's wired we fall back to a dedicated
// cat.log file so early-startup lines aren't lost.
var LogSink func(format string, args ...any)
func openDebugLog() *log.Logger {
// catLogger forwards Printf either to the host LogSink (preferred) or to a
// local file/stderr fallback. Keeps the call sites (debugLog.Printf(...))
// unchanged.
type catLogger struct{ fallback *log.Logger }
func (c *catLogger) Printf(format string, args ...any) {
if LogSink != nil {
LogSink("cat: "+format, args...)
return
}
if c.fallback != nil {
c.fallback.Printf(format, args...)
}
}
// debugLog writes CAT debug events so users can diagnose mode/freq mismatches
// without rebuilding with a console. Once LogSink is set, lines flow into the
// main opslog.log.
var debugLog = &catLogger{fallback: openFallbackLog()}
func openFallbackLog() *log.Logger {
base, err := os.UserConfigDir()
if err != nil {
return log.Default()
}
dir := filepath.Join(base, "HamLog")
dir := filepath.Join(base, "OpsLog")
if err := os.MkdirAll(dir, 0o755); err != nil {
return log.Default()
}
@@ -31,12 +49,12 @@ func openDebugLog() *log.Logger {
return log.New(f, "", log.LstdFlags|log.Lmicroseconds)
}
// DebugLogPath returns the path the cat.log file would be opened at, for
// surfacing in the UI / docs.
// DebugLogPath returns where the fallback cat.log lives, for surfacing in the
// UI / docs. When LogSink is wired, CAT lines are in the main app log instead.
func DebugLogPath() string {
base, err := os.UserConfigDir()
if err != nil {
return ""
}
return filepath.Join(base, "HamLog", "cat.log")
return filepath.Join(base, "OpsLog", "cat.log")
}
+80 -24
View File
@@ -160,42 +160,98 @@ func (o *OmniRig) ReadState() (RigState, error) {
func (o *OmniRig) SetFrequency(hz int64) error {
if o.rig == nil {
debugLog.Printf("OmniRig.SetFrequency(%d): NOT CONNECTED", hz)
return fmt.Errorf("not connected")
}
// OmniRig Freq is a Long (int32). Validate to avoid silent truncation.
if hz < 0 || hz > 0x7fffffff {
debugLog.Printf("OmniRig.SetFrequency(%d): out of int32 range", hz)
return fmt.Errorf("frequency out of OmniRig int32 range")
}
hz32 := int32(hz)
// Pick the right OmniRig property. Many rig .ini files only define a
// WRITE command for FreqA/FreqB but not the generic Freq — in which case
// PutProperty(Freq) silently succeeds but the rig never moves. Write to
// the active VFO's specific property when we know it; fall back to Freq.
prop := "FreqA"
if vfoVar, err := oleutil.GetProperty(o.rig, "Vfo"); err == nil {
switch omniRigVfo(vfoVar.Val) {
case "B", "BB", "BA":
prop = "FreqB"
case "A", "AA", "AB":
prop = "FreqA"
}
// Log the rig's writable-params, status and VFO state up front so a
// friend's session shows exactly what OmniRig reports for their rig.
status, statusStr, rigType := int64(-1), "", ""
if v, err := oleutil.GetProperty(o.rig, "Status"); err == nil {
status = v.Val
}
debugLog.Printf("OmniRig.SetFrequency(%d Hz / %.6f MHz) → %s", hz, float64(hz)/1e6, prop)
if _, err := oleutil.PutProperty(o.rig, prop, hz32); err != nil {
debugLog.Printf("OmniRig.SetFrequency(%s) error: %v — falling back to Freq", prop, err)
if _, err2 := oleutil.PutProperty(o.rig, "Freq", hz32); err2 != nil {
debugLog.Printf("OmniRig.SetFrequency(Freq) also failed: %v", err2)
return err2
}
if v, err := oleutil.GetProperty(o.rig, "StatusStr"); err == nil {
statusStr = v.ToString()
}
if v, err := oleutil.GetProperty(o.rig, "RigType"); err == nil {
rigType = v.ToString()
}
rawVfo, vfo := int64(-1), ""
if vfoVar, err := oleutil.GetProperty(o.rig, "Vfo"); err == nil {
rawVfo = vfoVar.Val
vfo = omniRigVfo(vfoVar.Val)
} else {
debugLog.Printf("OmniRig.SetFrequency: Vfo read error: %v", err)
}
split := int64(0)
if v, err := oleutil.GetProperty(o.rig, "Split"); err == nil {
split = v.Val
}
// What can this rig's .ini actually write? OmniRig exposes a WriteableParams
// bitmask — if FreqA/FreqB/Freq bits are missing, the write is a silent no-op.
writeable := int64(-1)
if v, err := oleutil.GetProperty(o.rig, "WriteableParams"); err == nil {
writeable = v.Val
}
debugLog.Printf("OmniRig.SetFrequency(%d Hz / %.6f MHz): rig=%q status=%d(%s) vfo=%q(raw=%d) split=%d writeableParams=0x%X",
hz, float64(hz)/1e6, rigType, status, statusStr, vfo, rawVfo, split, writeable)
// Pick the active VFO's specific property. Many rig .ini files only define
// a WRITE command for FreqA/FreqB but not the generic Freq.
prop := "FreqA"
switch vfo {
case "B", "BB", "BA":
prop = "FreqB"
case "A", "AA", "AB":
prop = "FreqA"
}
// Read back the active VFO freq after a short delay so the log shows
// whether the rig actually moved. Useful when the .ini accepts the write
// silently but the rig doesn't honour it (wrong WRITE command etc.).
if fv, err := oleutil.GetProperty(o.rig, "Freq"); err == nil {
debugLog.Printf("OmniRig.Freq immediately after Put = %d Hz", fv.Val)
wroteOK := false
if _, err := oleutil.PutProperty(o.rig, prop, hz32); err != nil {
debugLog.Printf("OmniRig.SetFrequency: PutProperty(%s) error: %v", prop, err)
} else {
debugLog.Printf("OmniRig.SetFrequency: PutProperty(%s, %d) OK", prop, hz32)
wroteOK = true
}
// Belt-and-suspenders: when NOT in split, also write the generic Freq.
// Icom .ini files commonly honour Freq (CI-V "set operating frequency")
// but ignore FreqA/FreqB, so the rig changed mode but never moved — this
// is exactly the IC-9100 "mode changes, freq doesn't" symptom.
if split == 0 {
if _, err := oleutil.PutProperty(o.rig, "Freq", hz32); err != nil {
debugLog.Printf("OmniRig.SetFrequency: PutProperty(Freq) error: %v", err)
if !wroteOK {
return err
}
} else {
debugLog.Printf("OmniRig.SetFrequency: PutProperty(Freq, %d) OK", hz32)
}
} else if !wroteOK {
return fmt.Errorf("OmniRig: could not write %s and split is on (won't touch generic Freq)", prop)
}
// Read back all three immediately. OmniRig is async (the CAT command is
// queued + sent over serial), so these may still show the OLD value for
// one poll cycle — but if they NEVER change in the next poll, the rig
// isn't honouring the write (wrong .ini WRITE command for this model).
fa, fb, fg := int64(-1), int64(-1), int64(-1)
if v, err := oleutil.GetProperty(o.rig, "FreqA"); err == nil {
fa = v.Val
}
if v, err := oleutil.GetProperty(o.rig, "FreqB"); err == nil {
fb = v.Val
}
if v, err := oleutil.GetProperty(o.rig, "Freq"); err == nil {
fg = v.Val
}
debugLog.Printf("OmniRig.SetFrequency: readback FreqA=%d FreqB=%d Freq=%d (target %d)", fa, fb, fg, hz)
return nil
}