Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package rtlamr for openSUSE:Factory checked 
in at 2026-11-01 22:25:06
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rtlamr (Old)
 and      /work/SRC/openSUSE:Factory/.rtlamr.new.1255 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rtlamr"

Sun Nov  1 22:25:06 2026 rev:2 rq:1394296 version:0.9.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/rtlamr/rtlamr.changes    2026-09-01 
15:48:15.373546212 +0200
+++ /work/SRC/openSUSE:Factory/.rtlamr.new.1255/rtlamr.changes  2026-11-01 
22:26:11.660911227 +0100
@@ -1,0 +2,21 @@
+Sun Nov  1 14:39:02 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 0.9.6:
+  * Signal processing switched from float64 to float32, with
+    combined filter and quantize step in the R900 stage
+  * Reworked error handling on top of stdlib errors: exit 1 when
+    the receiver stops on an error, propagate csv write errors,
+    fail gracefully on invalid message types
+  * Set the missed-sample threshold to 90% and log slow sample
+    consumption as a warning
+  * rtltcp connection arguments moved into rtlamr flags
+  * Fix nil-pointer dereference and IDM message handling
+  * Pin the scmplus JSON field name PacketCRC for compatibility
+- Drop rtlamr-no-debug-stdout-print.patch, contained upstream:
+  no more build directory printed to stdout, which corrupted
+  the first line of csv/json output
+- Regenerate vendor.tar.gz, now shipping only rtltcp
+- License is AGPL-3.0-only AND BSD-3-Clause: pkg/errors is gone,
+  so the BSD-2-Clause grant no longer applies
+
+-------------------------------------------------------------------

Old:
----
  rtlamr-0.9.5.tar.gz
  rtlamr-no-debug-stdout-print.patch

New:
----
  rtlamr-0.9.6.tar.gz

----------(Old B)----------
  Old:  * Pin the scmplus JSON field name PacketCRC for compatibility
- Drop rtlamr-no-debug-stdout-print.patch, contained upstream:
  no more build directory printed to stdout, which corrupted
----------(Old E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rtlamr.spec ++++++
--- /var/tmp/diff_new_pack.uQRVas/_old  2026-11-01 22:26:12.937964167 +0100
+++ /var/tmp/diff_new_pack.uQRVas/_new  2026-11-01 22:26:12.940964291 +0100
@@ -18,30 +18,21 @@
 
 
 Name:           rtlamr
-Version:        0.9.5
+Version:        0.9.6
 Release:        0
 Summary:        SDR receiver for Itron ERT compatible smart meters
 # Legal-Review-Notice: aggregate licence of the statically linked binary.
 # Tagged AGPL-3.0-only although rtlamr's own headers grant "version 3 or (at
 #   your option) any later version": the vendored github.com/bemasher/rtltcp
-#   ships NO licence text at the revision go.mod pins, and its upstream repo
-#   carries the bare AGPLv3 with no or-later grant. A later-version upgrade is
-#   therefore not granted for the aggregate, so -or-later would overstate it.
-#   Please confirm the rtltcp revision's status.
+#   grants no or-later upgrade (its sources carry no licence header and its
+#   repo ships the stock AGPLv3 text), so -or-later would overstate the
+#   aggregate. Please confirm the rtltcp revision's status.
 # BSD-3-Clause: in-tree r900/gf (Go Authors, r900/gf/LICENSE), imported by
-#   r900, plus vendored golang.org/x/xerrors.
-# BSD-2-Clause: vendored github.com/pkg/errors.
-# go.mod also requires github.com/sirupsen/logrus, but no source file imports
-# it, so go mod vendor omits it and it is not linked.
-License:        AGPL-3.0-only AND BSD-2-Clause AND BSD-3-Clause
+#   r900.
+License:        AGPL-3.0-only AND BSD-3-Clause
 URL:            https://github.com/bemasher/rtlamr
 Source:         
https://github.com/bemasher/rtlamr/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
 Source1:        vendor.tar.gz
-# PATCH-FIX-UPSTREAM rtlamr-no-debug-stdout-print.patch 
gh#bemasher/rtlamr@a1f6df8
-# -- init() printed the build dir to stdout, corrupting the first line of
-# csv/json output; upstream's fix is one commit past v0.9.5 and unreleased, so
-# drop this only once a release actually contains a1f6df8
-Patch0:         rtlamr-no-debug-stdout-print.patch
 BuildRequires:  golang(API) >= 1.21
 
 %description
@@ -52,8 +43,6 @@
 %autosetup -p1 -a1
 # Notices for the BSD-licensed code compiled in (see Legal-Review-Notice)
 cp r900/gf/LICENSE LICENSE.r900-gf
-cp vendor/github.com/pkg/errors/LICENSE LICENSE.pkg-errors
-cp vendor/golang.org/x/xerrors/LICENSE LICENSE.xerrors
 
 %build
 # -trimpath: keep the build root out of the binary (reproducible builds); it
@@ -70,7 +59,7 @@
 go test -mod=vendor ./...
 
 %files
-%license LICENSE LICENSE.pkg-errors LICENSE.r900-gf LICENSE.xerrors
+%license LICENSE LICENSE.r900-gf
 %doc README.md
 %{_bindir}/rtlamr
 

++++++ rtlamr-0.9.5.tar.gz -> rtlamr-0.9.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/csv/csv.go new/rtlamr-0.9.6/csv/csv.go
--- old/rtlamr-0.9.5/csv/csv.go 2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/csv/csv.go 2026-10-31 23:50:59.000000000 +0100
@@ -2,9 +2,8 @@
 
 import (
        "encoding/csv"
+       "fmt"
        "io"
-
-       "golang.org/x/xerrors"
 )
 
 // Produces a list of fields making up a record.
@@ -26,13 +25,21 @@
 // newline character. Value given must implement the Recorder interface.
 func (enc *Encoder) Encode(v interface{}) (err error) {
        defer func() {
-               if err, _ = recover().(error); err != nil {
-                       err = xerrors.Errorf("recovered: %w", err)
+               // Only override err if we actually recovered from a panic; 
otherwise
+               // keep whatever error the write returned.
+               switch r := recover().(type) {
+               case nil:
+               case error:
+                       err = fmt.Errorf("recovered: %w", r)
+               default:
+                       err = fmt.Errorf("recovered: %v", r)
                }
        }()
 
-       err = enc.w.Write(v.(Recorder).Record())
+       if err = enc.w.Write(v.(Recorder).Record()); err != nil {
+               return err
+       }
        enc.w.Flush()
 
-       return nil
+       return enc.w.Error()
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/csv/csv_test.go 
new/rtlamr-0.9.6/csv/csv_test.go
--- old/rtlamr-0.9.5/csv/csv_test.go    2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/csv/csv_test.go    2026-10-31 23:50:59.000000000 +0100
@@ -3,10 +3,9 @@
 import (
        "bytes"
        "encoding/csv"
+       "errors"
        "runtime"
        "testing"
-
-       "golang.org/x/xerrors"
 )
 
 func TestRecorderNil(t *testing.T) {
@@ -42,7 +41,37 @@
        err := enc.Encode(NonRecorder{})
 
        var runtimeErr runtime.Error
-       if !xerrors.As(err, &runtimeErr) {
+       if !errors.As(err, &runtimeErr) {
                t.Fatalf("%+v\n", runtimeErr)
        }
 }
+
+type failWriter struct{}
+
+func (failWriter) Write([]byte) (int, error) { return 0, errWrite }
+
+var errWrite = errors.New("write failed")
+
+// TestWriteError checks that an error writing to the underlying stream is
+// returned. Previously Encode always returned nil on a non-panicking path.
+func TestWriteError(t *testing.T) {
+       enc := NewEncoder(failWriter{})
+
+       if err := enc.Encode(Msg{}); !errors.Is(err, errWrite) {
+               t.Fatalf("Encode() = %v, want %v", err, errWrite)
+       }
+}
+
+type panicRecorder struct{}
+
+func (panicRecorder) Record() []string { panic("not an error value") }
+
+// TestNonErrorPanic checks that a panic with a non-error value is still
+// reported rather than silently swallowed.
+func TestNonErrorPanic(t *testing.T) {
+       enc := NewEncoder(&bytes.Buffer{})
+
+       if err := enc.Encode(panicRecorder{}); err == nil {
+               t.Fatal("Encode() = nil, want an error")
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/go.mod new/rtlamr-0.9.6/go.mod
--- old/rtlamr-0.9.5/go.mod     2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/go.mod     2026-10-31 23:50:59.000000000 +0100
@@ -1,10 +1,5 @@
 module github.com/bemasher/rtlamr
 
-go 1.12
+go 1.21
 
-require (
-       github.com/bemasher/rtltcp v0.0.0-20151011062038-3aed81c166c5
-       github.com/pkg/errors v0.8.1
-       github.com/sirupsen/logrus v1.9.3
-       golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898
-)
+require github.com/bemasher/rtltcp v0.0.0-20261031224328-8792921d55f4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/go.sum new/rtlamr-0.9.6/go.sum
--- old/rtlamr-0.9.5/go.sum     2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/go.sum     2026-10-31 23:50:59.000000000 +0100
@@ -1,22 +1,2 @@
-github.com/bemasher/rtltcp v0.0.0-20151011062038-3aed81c166c5 
h1:QFs9NpZKQY/WPjOfgNN8Qt83scnnhDueQrnjZOG884M=
-github.com/bemasher/rtltcp v0.0.0-20151011062038-3aed81c166c5/go.mod 
h1:O6JJfPo2Vr2FA+N401mWyEVhwq5Wo/z1dfX+tIKGRUU=
-github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
-github.com/pkg/errors v0.8.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.9.3 
h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
-github.com/sirupsen/logrus v1.9.3/go.mod 
h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0 
h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 
h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 
h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 
h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c 
h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+github.com/bemasher/rtltcp v0.0.0-20261031224328-8792921d55f4 
h1:pcRtQ998Ux2r/amGzlUCejEddwac8U3bkM5PxsI1gYM=
+github.com/bemasher/rtltcp v0.0.0-20261031224328-8792921d55f4/go.mod 
h1:9ivUuu07OXdqEEu1OwUojD1ORWKit39zDLr1cglzvcE=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/idm/idm.go new/rtlamr-0.9.6/idm/idm.go
--- old/rtlamr-0.9.5/idm/idm.go 2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/idm/idm.go 2026-10-31 23:50:59.000000000 +0100
@@ -128,9 +128,9 @@
        idm.ERTSerialNumber = binary.BigEndian.Uint32(data.Bytes[9:13])
        idm.ConsumptionIntervalCount = data.Bytes[13]
        idm.ModuleProgrammingState = data.Bytes[14]
-       idm.TamperCounters = data.Bytes[15:21]
+       idm.TamperCounters = append([]byte(nil), data.Bytes[15:21]...)
        idm.AsynchronousCounters = binary.BigEndian.Uint16(data.Bytes[21:23])
-       idm.PowerOutageFlags = data.Bytes[23:29]
+       idm.PowerOutageFlags = append([]byte(nil), data.Bytes[23:29]...)
        idm.LastConsumptionCount = binary.BigEndian.Uint32(data.Bytes[29:33])
 
        offset := 264
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/main.go new/rtlamr-0.9.6/main.go
--- old/rtlamr-0.9.5/main.go    2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/main.go    2026-10-31 23:50:59.000000000 +0100
@@ -19,6 +19,7 @@
 import (
        "bytes"
        "context"
+       "errors"
        "flag"
        "fmt"
        "io"
@@ -32,10 +33,9 @@
        "sync"
        "time"
 
-       "github.com/pkg/errors"
-
        "github.com/bemasher/rtlamr/protocol"
        "github.com/bemasher/rtltcp"
+       "github.com/bemasher/rtltcp/si"
 
        _ "github.com/bemasher/rtlamr/idm"
        _ "github.com/bemasher/rtlamr/netidm"
@@ -47,20 +47,25 @@
 
 var rcvr Receiver
 
+// errSingleDone stops the receiver once -single has heard from every meter.
+// It is a normal exit, not a failure.
+var errSingleDone = errors.New("single: received messages from all meters")
+
 type Receiver struct {
        rtltcp.SDR
+       flags SdrFlags
+
        d  protocol.Decoder
        fc protocol.FilterChain
 
-       ctx    context.Context
-       cancel context.CancelFunc
-       wg     *sync.WaitGroup
-
-       err error
+       ctx  context.Context
+       canc context.CancelCauseFunc
+       wg   *sync.WaitGroup
 }
 
-func (rcvr *Receiver) NewReceiver() {
-       rcvr.ctx, rcvr.cancel = context.WithCancel(context.Background())
+func (rcvr *Receiver) NewReceiver(ctx context.Context) {
+       rcvr.ctx, rcvr.canc = context.WithCancelCause(ctx)
+
        rcvr.wg = &sync.WaitGroup{}
 
        rcvr.d = protocol.NewDecoder()
@@ -78,7 +83,8 @@
        for name := range msgType {
                p, err := protocol.NewParser(name, *symbolLength)
                if err != nil {
-                       slog.Error("message type", "error", err)
+                       rcvr.canc(fmt.Errorf("protocol.NewParser: %w", err))
+                       return
                }
 
                rcvr.d.RegisterProtocol(p)
@@ -88,9 +94,14 @@
        rcvr.d.Allocate()
 
        // Connect to rtl_tcp server.
-       if rcvr.err = rcvr.Connect(nil); rcvr.err != nil {
-               slog.Error("receiver connect", "error", errors.Wrap(rcvr.err, 
"rcvr.Connect"))
-               os.Exit(1)
+       if err := rcvr.Connect(nil); err != nil {
+               rcvr.canc(fmt.Errorf("rcvr.Connect: %w", err))
+               return
+       }
+
+       if err := rcvr.HandleFlags(); err != nil {
+               rcvr.canc(fmt.Errorf("rcvr.HandleFlags: %w", err))
+               return
        }
 
        cfg := rcvr.d.Cfg
@@ -99,9 +110,9 @@
        flag.Visit(func(f *flag.Flag) {
                switch f.Name {
                case "centerfreq":
-                       cfg.CenterFreq = uint32(rcvr.Flags.CenterFreq)
+                       cfg.CenterFreq = uint32(rcvr.flags.CenterFreq)
                case "samplerate":
-                       cfg.SampleRate = int(rcvr.Flags.SampleRate)
+                       cfg.SampleRate = int(rcvr.flags.SampleRate)
                case "gainbyindex", "tunergainmode", "tunergain", "agcmode":
                        gainFlagSet = true
                case "unique":
@@ -129,8 +140,90 @@
        slog.Info("rtl_tcp", "GainCount", rcvr.SDR.Info.GainCount)
 }
 
+type SdrFlags struct {
+       ServerAddr     string
+       CenterFreq     si.ScientificNotation
+       SampleRate     si.ScientificNotation
+       TunerGainMode  bool
+       TunerGain      float64
+       FreqCorrection int
+       TestMode       bool
+       AgcMode        bool
+       DirectSampling bool
+       OffsetTuning   bool
+       RtlXtalFreq    uint
+       TunerXtalFreq  uint
+       GainByIndex    uint
+}
+
+// Registers command line flags for rtltcp commands.
+func (rcvr *Receiver) RegisterFlags() {
+       flag.StringVar(&rcvr.flags.ServerAddr, "server", "127.0.0.1:1234", 
"address or hostname of rtl_tcp instance")
+       flag.Var(&rcvr.flags.CenterFreq, "centerfreq", "center frequency to 
receive on")
+       flag.Lookup("centerfreq").DefValue = "100M"
+       flag.Var(&rcvr.flags.SampleRate, "samplerate", "sample rate")
+       flag.Lookup("samplerate").DefValue = "2.4M"
+       flag.BoolVar(&rcvr.flags.TunerGainMode, "tunergainmode", false, 
"enable/disable tuner gain")
+       flag.Float64Var(&rcvr.flags.TunerGain, "tunergain", 0.0, "set tuner 
gain in dB")
+       flag.IntVar(&rcvr.flags.FreqCorrection, "freqcorrection", 0, "frequency 
correction in ppm")
+       flag.BoolVar(&rcvr.flags.TestMode, "testmode", false, "enable/disable 
test mode")
+       flag.BoolVar(&rcvr.flags.AgcMode, "agcmode", false, "enable/disable rtl 
agc")
+       flag.BoolVar(&rcvr.flags.DirectSampling, "directsampling", false, 
"enable/disable direct sampling")
+       flag.BoolVar(&rcvr.flags.OffsetTuning, "offsettuning", false, 
"enable/disable offset tuning")
+       flag.UintVar(&rcvr.flags.RtlXtalFreq, "rtlxtalfreq", 0, "set rtl xtal 
frequency")
+       flag.UintVar(&rcvr.flags.TunerXtalFreq, "tunerxtalfreq", 0, "set tuner 
xtal frequency")
+       flag.UintVar(&rcvr.flags.GainByIndex, "gainbyindex", 0, "set gain by 
index")
+}
+
+// Parses flags and executes commands associated with each flag. Should only
+// be called once connected to rtl_tcp.
+func (rcvr *Receiver) HandleFlags() (err error) {
+       // Catch any errors panicked while visiting flags.
+       defer func() {
+               if r := recover(); r != nil {
+                       err = r.(error)
+               }
+       }()
+
+       flag.CommandLine.Visit(func(f *flag.Flag) {
+               var err error
+               switch f.Name {
+               case "centerfreq":
+                       err = rcvr.SetCenterFreq(uint32(rcvr.flags.CenterFreq))
+               case "samplerate":
+                       err = rcvr.SetSampleRate(uint32(rcvr.flags.SampleRate))
+               case "tunergainmode":
+                       err = rcvr.SetGainMode(rcvr.flags.TunerGainMode)
+               case "tunergain":
+                       err = rcvr.SetGain(uint32(rcvr.flags.TunerGain * 10.0))
+               case "freqcorrection":
+                       err = 
rcvr.SetFreqCorrection(int32(rcvr.flags.FreqCorrection))
+               case "testmode":
+                       err = rcvr.SetTestMode(rcvr.flags.TestMode)
+               case "agcmode":
+                       err = rcvr.SetAGCMode(rcvr.flags.AgcMode)
+               case "directsampling":
+                       err = rcvr.SetDirectSampling(rcvr.flags.DirectSampling)
+               case "offsettuning":
+                       err = rcvr.SetOffsetTuning(rcvr.flags.OffsetTuning)
+               case "rtlxtalfreq":
+                       err = 
rcvr.SetRTLXtalFreq(uint32(rcvr.flags.RtlXtalFreq))
+               case "tunerxtalfreq":
+                       err = 
rcvr.SetTunerXtalFreq(uint32(rcvr.flags.TunerXtalFreq))
+               case "gainbyindex":
+                       err = 
rcvr.SetGainByIndex(uint32(rcvr.flags.GainByIndex))
+               }
+
+               // If we encounter an error, panic to catch in parent scope.
+               if err != nil {
+                       panic(err)
+               }
+       })
+
+       return
+}
+
 func (rcvr *Receiver) Close() {
-       rcvr.cancel()
        rcvr.wg.Wait()
        rcvr.SDR.Close()
 }
@@ -157,7 +250,7 @@
 
        // Read and send sample blocks to the decoder.
        go func() {
-               defer rcvr.cancel()
+               defer rcvr.canc(nil)
                defer close(blockCh)
                defer rcvr.wg.Done()
 
@@ -168,9 +261,9 @@
                for {
                        block := make([]byte, rcvr.d.Cfg.BlockSize2)
 
-                       rcvr.err = rcvr.SetDeadline(time.Now().Add(5 * 
time.Second))
-                       if rcvr.err != nil {
-                               rcvr.err = errors.Wrap(rcvr.err, 
"rcvr.SetDeadline")
+                       err := rcvr.SetDeadline(time.Now().Add(5 * time.Second))
+                       if err != nil {
+                               rcvr.canc(fmt.Errorf("rcvr.SetDeadline: %w", 
err))
                                return
                        }
 
@@ -178,9 +271,9 @@
                        for offset := 0; offset < len(block); {
                                var n int
 
-                               n, rcvr.err = rcvr.Read(block[offset:])
-                               if rcvr.err != nil {
-                                       rcvr.err = errors.Wrap(rcvr.err, 
"rcvr.Read")
+                               n, err = rcvr.Read(block[offset:])
+                               if err != nil {
+                                       rcvr.canc(fmt.Errorf("rcvr.Read: %w", 
err))
                                        return
                                }
 
@@ -190,8 +283,9 @@
 
                        select {
                        case <-tick:
-                               if bytesRead>>1 < rcvr.d.Cfg.SampleRate {
-                                       slog.Error("not keeping up with 
rtl_tcp", "rate", bytesRead>>1)
+                               // Complain if received samples are less than 
90% configured rate.
+                               if bytesRead>>1 < (rcvr.d.Cfg.SampleRate * 9 / 
10) {
+                                       slog.Warn("not keeping up with 
rtl_tcp", "rate", bytesRead>>1)
                                }
                                bytesRead = 0
                        default:
@@ -207,7 +301,7 @@
        }()
 
        go func() {
-               defer rcvr.cancel()
+               defer rcvr.canc(nil)
                defer rcvr.wg.Done()
 
                for {
@@ -262,10 +356,9 @@
                                        }
 
                                        // Encode the message
-                                       rcvr.err = encoder.Encode(logMsg)
-                                       rcvr.err = errors.Wrap(rcvr.err, 
"encoder.Encode")
-
-                                       if rcvr.err != nil {
+                                       err := encoder.Encode(logMsg)
+                                       if err != nil {
+                                               
rcvr.canc(fmt.Errorf("encoder.Encode: %w", err))
                                                return
                                        }
 
@@ -286,7 +379,7 @@
                                                os.Exit(1)
                                        }
                                        if *single && len(meterID.UintMap) == 0 
{
-                                               rcvr.cancel()
+                                               rcvr.canc(errSingleDone)
                                                return
                                        }
                                }
@@ -304,7 +397,6 @@
        if ok {
                dir = filepath.Dir(file)
                dir = filepath.Dir(dir) + string(filepath.Separator)
-               fmt.Println(dir)
        }
 
        slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr,
@@ -321,11 +413,17 @@
 }
 
 func main() {
+       os.Exit(run())
+}
+
+// run is main's body. Returning the exit code (rather than calling os.Exit
+// here) lets the deferred cleanup below finish first, and lets a panic crash
+// with its stack trace instead of being masked by an exit status.
+func run() int {
        rcvr.RegisterFlags()
        RegisterFlags()
        EnvOverride()
        flag.Parse()
-       rcvr.HandleFlags()
 
        if *version {
                if info, ok := debug.ReadBuildInfo(); ok {
@@ -333,46 +431,68 @@
                } else {
                        slog.Error("could not read build info")
                }
-               os.Exit(0)
+               return 0
        }
 
        HandleFlags()
 
-       rcvr.NewReceiver()
+       // Setup signal channel for interruption.
+       ctx, canc := signal.NotifyContext(context.Background(), os.Interrupt)
+       defer canc()
 
+       // Make sure sampleWriter is closed.
        defer func() {
                if c, ok := sampleWriter.(io.Closer); ok {
+                       slog.Info("closing sampleWriter")
                        c.Close()
                }
-               rcvr.Close()
-
-               if rcvr.err != nil {
-                       slog.Error("receiver", "error", rcvr.err)
-                       os.Exit(1)
-               }
        }()
 
-       start := time.Now()
-       rcvr.Run()
-
-       // Setup signal channel for interruption.
-       sigCh := make(chan os.Signal, 1)
-       signal.Notify(sigCh, os.Interrupt)
-
-       // Setup time limit channel
-       timeLimitCh := make(<-chan time.Time, 1)
+       // Set context timeout when provided.
        if *timeLimit != 0 {
-               timeLimitCh = time.After(*timeLimit)
+               ctx, canc = context.WithTimeout(ctx, *timeLimit)
+               defer canc()
+       }
+
+       rcvr.NewReceiver(ctx)
+       if rcvr.ctx.Err() != nil {
+               slog.Error("rcvr.NewReceiver", "error", context.Cause(rcvr.ctx))
+               return 1
        }
+       defer rcvr.Close()
+
+       rcvr.HandleFlags()
+
+       rcvr.Run()
 
        select {
-       case sig := <-sigCh:
-               slog.Info("signal received", "signal", sig)
-       case <-timeLimitCh:
-               slog.Info("time limit reached:", "since", time.Since(start))
+       case <-ctx.Done():
+               err := context.Cause(ctx)
+               slog.Info("main context cancelled", "cause", err)
        case <-rcvr.ctx.Done():
-               slog.Info("receiver context cancelled")
+               err := context.Cause(rcvr.ctx)
+               slog.Info("receiver context cancelled", "cause", err)
+       }
+
+       if err := exitError(ctx, rcvr.ctx); err != nil {
+               slog.Error("receiver stopped", "error", err)
+               return 1
        }
+       return 0
+}
 
-       rcvr.Close()
+// exitError returns the error that stopped the receiver, or nil if it stopped
+// normally: interrupted, -duration elapsed, or -single heard from every meter.
+func exitError(mainCtx, rcvrCtx context.Context) error {
+       cause := context.Cause(rcvrCtx)
+       switch {
+       case cause == nil,
+               errors.Is(cause, context.Canceled),
+               errors.Is(cause, errSingleDone):
+               return nil
+       case mainCtx.Err() != nil && cause == context.Cause(mainCtx):
+               // Inherited from main's context: interrupt or -duration.
+               return nil
+       }
+       return cause
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/protocol/decode.go 
new/rtlamr-0.9.6/protocol/decode.go
--- old/rtlamr-0.9.5/protocol/decode.go 2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/protocol/decode.go 2026-10-31 23:50:59.000000000 +0100
@@ -41,34 +41,15 @@
        CenterFreq   uint32
 }
 
-func (d Decoder) Log() {
-       log.Println("CenterFreq:", d.Cfg.CenterFreq)
-       log.Println("SampleRate:", d.Cfg.SampleRate)
-       log.Println("DataRate:", d.Cfg.DataRate)
-       log.Println("ChipLength:", d.Cfg.ChipLength)
-       log.Println("PreambleSymbols:", d.Cfg.PreambleSymbols)
-       log.Println("PreambleLength:", d.Cfg.PreambleLength)
-       log.Println("PacketSymbols:", d.Cfg.PacketSymbols)
-       log.Println("PacketLength:", d.Cfg.PacketLength)
-
-       var preambles []string
-       for preamble, _ := range d.preambleStrs {
-               preambles = append(preambles, preamble)
-       }
-
-       log.Println("Protocols:", strings.Join(d.protocols, ","))
-       log.Println("Preambles:", strings.Join(preambles, ","))
-}
-
 // Decoder contains buffers and radio configuration.
 type Decoder struct {
        Cfg PacketConfig
        wg  *sync.WaitGroup
 
-       Signal    []float64
+       Signal    []float32
        Quantized []byte
 
-       csum  []float64
+       csum  []float32
        demod Demodulator
 
        preambleStrs map[string]bool
@@ -89,6 +70,25 @@
        }
 }
 
+func (d Decoder) Log() {
+       log.Println("CenterFreq:", d.Cfg.CenterFreq)
+       log.Println("SampleRate:", d.Cfg.SampleRate)
+       log.Println("DataRate:", d.Cfg.DataRate)
+       log.Println("ChipLength:", d.Cfg.ChipLength)
+       log.Println("PreambleSymbols:", d.Cfg.PreambleSymbols)
+       log.Println("PreambleLength:", d.Cfg.PreambleLength)
+       log.Println("PacketSymbols:", d.Cfg.PacketSymbols)
+       log.Println("PacketLength:", d.Cfg.PacketLength)
+
+       var preambles []string
+       for preamble := range d.preambleStrs {
+               preambles = append(preambles, preamble)
+       }
+
+       log.Println("Protocols:", strings.Join(d.protocols, ","))
+       log.Println("Preambles:", strings.Join(preambles, ","))
+}
+
 func max(a, b int) int {
        if a > b {
                return a
@@ -141,10 +141,10 @@
        d.Cfg.BufferLength = d.Cfg.PacketLength + d.Cfg.BlockSize
 
        // Allocate necessary buffers.
-       d.Signal = make([]float64, d.Cfg.BlockSize+d.Cfg.SymbolLength)
+       d.Signal = make([]float32, d.Cfg.BlockSize+d.Cfg.SymbolLength)
        d.Quantized = make([]byte, d.Cfg.BufferLength)
 
-       d.csum = make([]float64, len(d.Signal)+1)
+       d.csum = make([]float32, len(d.Signal)+1)
 
        // Calculate magnitude lookup table specified by -fastmag flag.
        d.demod = NewMagLUT()
@@ -157,8 +157,6 @@
        d.sIdxB = make([]int, 0, d.Cfg.BlockSize)
 
        d.packed = make([]byte, (d.Cfg.BlockSize+d.Cfg.PreambleLength+7)>>3)
-
-       return
 }
 
 // Decode accepts a sample block and returns a channel of messages.
@@ -199,26 +197,26 @@
 }
 
 // A Demodulator knows how to demodulate an array of uint8 IQ samples into an
-// array of float64 samples.
+// array of float32 samples.
 type Demodulator interface {
-       Execute([]byte, []float64)
+       Execute([]byte, []float32)
 }
 
 // Default Magnitude Lookup Table
-type MagLUT []float64
+type MagLUT []float32
 
 // Pre-computes normalized squares with most common DC offset for rtl-sdr 
dongles.
 func NewMagLUT() (lut MagLUT) {
-       lut = make([]float64, 0x100)
+       lut = make([]float32, 0x100)
        for idx := range lut {
-               lut[idx] = (127.5 - float64(idx)) / 127.5
+               lut[idx] = (127.5 - float32(idx)) / 127.5
                lut[idx] *= lut[idx]
        }
        return
 }
 
 // Calculates complex magnitude on given IQ stream writing result to output.
-func (lut MagLUT) Execute(input []byte, output []float64) {
+func (lut MagLUT) Execute(input []byte, output []float32) {
        i := 0
        for idx := range output {
                output[idx] = lut[input[i]] + lut[input[i+1]]
@@ -228,10 +226,10 @@
 
 // Matched filter for Manchester coded signals. Output signal's sign at each
 // sample determines the bit-value due to Manchester symbol odd symmetry.
-func (d Decoder) Filter(input []float64, output []byte) {
+func (d Decoder) Filter(input []float32, output []byte) {
        // Computing the cumulative summation over the signal simplifies
        // filtering to the difference of a pair of subtractions.
-       var sum float64
+       var sum float32
        for idx, v := range input {
                sum += v
                d.csum[idx+1] = sum
@@ -242,20 +240,18 @@
        upper := d.csum[d.Cfg.SymbolLength:]
        for idx, l := range lower[:len(output)] {
                f := (l - d.csum[idx]) - (upper[idx] - l)
-               output[idx] = 1 - byte(math.Float64bits(f)>>63)
+               output[idx] = 1 - byte(math.Float32bits(f)>>31)
        }
-
-       return
 }
 
 // Return a list of indices into the quantized signal at which a valid preamble
 // exists.
-// 1. Pack the quantized signal into bytes.
-// 2. Build a list of indices by eliminating bytes that contain no bits 
matching
-//    the first bit of the preamble.
-// 3. Continue eliminating indices at which the preamble cannot exist.
-// 4. Convert indices from byte-based to sample-based.
-// 5. Check each of these indices for the preamble.
+//  1. Pack the quantized signal into bytes.
+//  2. Build a list of indices by eliminating bytes that contain no bits 
matching
+//     the first bit of the preamble.
+//  3. Continue eliminating indices at which the preamble cannot exist.
+//  4. Convert indices from byte-based to sample-based.
+//  5. Check each of these indices for the preamble.
 func (d *Decoder) Search(preamble []byte) []int {
        symLenByte := d.Cfg.SymbolLength >> 3
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/protocol/parse.go 
new/rtlamr-0.9.6/protocol/parse.go
--- old/rtlamr-0.9.5/protocol/parse.go  2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/protocol/parse.go  2026-10-31 23:50:59.000000000 +0100
@@ -21,7 +21,7 @@
 type NewParserFunc func(symbolLength int) Parser
 
 // Given a name and a parser, register a parser for use.
-// Later used by underscore improting each parser package:
+// Later used by underscore importing each parser package:
 //
 // import _ "github.com/bemasher/rtlamr/scm"
 //
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/r900/r900.go 
new/rtlamr-0.9.6/r900/r900.go
--- old/rtlamr-0.9.5/r900/r900.go       2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/r900/r900.go       2026-10-31 23:50:59.000000000 +0100
@@ -19,7 +19,6 @@
 import (
        "bytes"
        "fmt"
-       "math"
        "strconv"
        "sync"
 
@@ -36,7 +35,6 @@
 }
 
 func NewPacketConfig(chipLength int) (cfg protocol.PacketConfig) {
-
        return
 }
 
@@ -46,9 +44,8 @@
        field *gf.Field
        rsBuf [31]byte
 
-       signal    []float64
-       csum      []float64
-       filtered  [][3]float64
+       signal    []float32
+       csum      []float32
        quantized []byte
 
        once sync.Once
@@ -77,12 +74,12 @@
        p.Decoder = d
 }
 
-func (p Parser) Cfg() protocol.PacketConfig {
+func (p *Parser) Cfg() protocol.PacketConfig {
        return p.cfg
 }
 
 // Perform matched filtering.
-func (p Parser) filter() {
+func (p *Parser) filter() {
        // This function computes the convolution of each symbol kernel with the
        // signal. The naive approach requires for each symbol to calculate the
        // summation of samples between a pair of indices.
@@ -96,7 +93,7 @@
        // signal. This reduces each summation to the difference between the two
        // indices of the cumulative sum.
 
-       var sum float64
+       var sum float32
        for idx, v := range p.signal {
                sum += v
                p.csum[idx+1] = sum
@@ -114,58 +111,57 @@
        // simplification but is necessary for efficiency.
 
        cfg := p.Decoder.Cfg
-       for idx := 0; idx < cfg.BufferLength-cfg.ChipLength*4; idx++ {
-               c0 := p.csum[idx]
-               c1 := p.csum[idx+cfg.ChipLength] * 2
-               c2 := p.csum[idx+cfg.ChipLength*2] * 2
-               c3 := p.csum[idx+cfg.ChipLength*3] * 2
-               c4 := p.csum[idx+cfg.ChipLength*4]
-
-               p.filtered[idx][0] = c2 - c4 - c0           // 1100
-               p.filtered[idx][1] = c1 - c2 + c3 - c4 - c0 // 1010
-               p.filtered[idx][2] = c1 - c3 + c4 - c0      // 1001
-       }
-}
+       cl := cfg.ChipLength
+       cl2 := cl * 2
+       cl3 := cl * 3
+       cl4 := cl * 4
+
+       limit := cfg.BufferLength - cl4
 
-// Determine the symbol that exists at each sample of the signal.
-func (p Parser) quantize() {
-       // 0 0011, 3 1100
-       // 1 0101, 4 1010
-       // 2 0110, 5 1001
+       for idx := 0; idx < limit; idx++ {
+               c0 := p.csum[idx]
+               c1 := p.csum[idx+cl] + p.csum[idx+cl]
+               c2 := p.csum[idx+cl2] + p.csum[idx+cl2]
+               c3 := p.csum[idx+cl3] + p.csum[idx+cl3]
+               c4 := p.csum[idx+cl4]
+
+               a0 := c2 - c4 - c0           // 1100
+               a1 := c1 - c2 + c3 - c4 - c0 // 1010
+               a2 := c1 - c3 + c4 - c0      // 1001
 
-       for idx, vec := range p.filtered {
+               // Quantize in the same loop
+               maxAbs := abs(a0)
                argmax := byte(0)
-               max := math.Abs(vec[0])
+               if abs(a1) > maxAbs {
 
-               // If v1 is larger than v0, update max and argmax.
-               if v1 := math.Abs(vec[1]); v1 > max {
-                       max = v1
+                       maxAbs = abs(a1)
                        argmax = 1
                }
-
-               // If v2 is larger than the greater of v1 or v0, update max and 
argmax.
-               if v2 := math.Abs(vec[2]); v2 > max {
-                       max = v2
+               if abs(a2) > maxAbs {
+                       maxAbs = abs(a2)
                        argmax = 2
                }
 
-               // Set the output symbol index.
                p.quantized[idx] = argmax
-
-               // If the sign is negative, jump to the index of the inverted 
symbol.
-               if vec[argmax] > 0 {
+               if [3]float32{a0, a1, a2}[argmax] > 0 {
                        p.quantized[idx] += 3
                }
        }
 }
 
+func abs[F float32 | float64](x F) F {
+       if x < 0 {
+               return -x
+       }
+       return x
+}
+
 // Given a list of indices the preamble exists at, decode and parse a message.
 func (p *Parser) Parse(pkts []protocol.Data, msgCh chan protocol.Message, wg 
*sync.WaitGroup) {
        p.once.Do(func() {
                p.cfg = p.Decoder.Cfg
-               p.signal = make([]float64, p.Decoder.Cfg.BufferLength)
-               p.csum = make([]float64, p.Decoder.Cfg.BufferLength+1)
-               p.filtered = make([][3]float64, p.Decoder.Cfg.BufferLength)
+               p.signal = make([]float32, p.Decoder.Cfg.BufferLength)
+               p.csum = make([]float32, p.Decoder.Cfg.BufferLength+1)
                p.quantized = make([]byte, p.Decoder.Cfg.BufferLength)
        })
 
@@ -174,7 +170,6 @@
        copy(p.signal[cfg.PacketLength:], p.Decoder.Signal[cfg.SymbolLength:])
 
        p.filter()
-       p.quantize()
 
        preambleLength := cfg.PreambleLength
        chipLength := cfg.ChipLength
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/scmplus/scmplus.go 
new/rtlamr-0.9.6/scmplus/scmplus.go
--- old/rtlamr-0.9.5/scmplus/scmplus.go 2026-04-25 22:35:30.000000000 +0200
+++ new/rtlamr-0.9.6/scmplus/scmplus.go 2026-10-31 23:50:59.000000000 +0100
@@ -99,7 +99,7 @@
        EndpointID   uint32 `xml:",attr"`
        Consumption  uint32 `xml:",attr"`
        Tamper       uint16 `xml:",attr"`
-       PacketCRC    uint16 `xml:"Checksum,attr",json:"Checksum"`
+       PacketCRC    uint16 `xml:"Checksum,attr"`
 }
 
 func NewSCM(data protocol.Data) (scm SCM) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/rtlamr-0.9.5/vendor/github.com/bemasher/rtltcp/README.md 
new/rtlamr-0.9.6/vendor/github.com/bemasher/rtltcp/README.md
--- old/rtlamr-0.9.5/vendor/github.com/bemasher/rtltcp/README.md        
1970-01-01 01:00:00.000000000 +0100
+++ new/rtlamr-0.9.6/vendor/github.com/bemasher/rtltcp/README.md        
2026-10-31 23:50:59.000000000 +0100
@@ -0,0 +1,7 @@
+## rtltcp
+
+Go wrapper for the TCP protocol implemented by the `rtl_tcp` tool for Realtek 
DVB-T based SDR's by OsmoSDR. More documentation to come.
+
+## Documentation
+
+[![GoDoc](https://godoc.org/github.com/bemasher/rtltcp?status.png)](https://godoc.org/github.com/bemasher/rtltcp)
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/rtlamr-0.9.5/vendor/github.com/bemasher/rtltcp/rtltcp.go 
new/rtlamr-0.9.6/vendor/github.com/bemasher/rtltcp/rtltcp.go
--- old/rtlamr-0.9.5/vendor/github.com/bemasher/rtltcp/rtltcp.go        
1970-01-01 01:00:00.000000000 +0100
+++ new/rtlamr-0.9.6/vendor/github.com/bemasher/rtltcp/rtltcp.go        
2026-10-31 23:50:59.000000000 +0100
@@ -0,0 +1,210 @@
+// This package provides a wrapper for the TCP protocol implemented by the 
rtl_tcp tool used with Realtek DVB-T based SDR's.
+package rtltcp
+
+import (
+       "encoding/binary"
+       "fmt"
+       "net"
+)
+
+var magic = [...]byte{'R', 'T', 'L', '0'}
+
+// Contains dongle information and an embedded tcp connection to the spectrum 
server
+type SDR struct {
+       *net.TCPConn
+       Info Info
+}
+
+// Give an address of the form "127.0.0.1:1234" connects to the spectrum
+// server at the given address or returns an error. The user is responsible
+// for closing this connection. If addr is nil, use "127.0.0.1:1234" or
+// command line flag value.
+func (sdr *SDR) Connect(addr *net.TCPAddr) (err error) {
+       if addr == nil {
+               // Parse and resolve rtl_tcp server address.
+               addr, err = net.ResolveTCPAddr("tcp", "127.0.0.1:1234")
+               if err != nil {
+                       return
+               }
+       }
+
+       sdr.TCPConn, err = net.DialTCP("tcp", nil, addr)
+       if err != nil {
+               err = fmt.Errorf("net.DialTCP: %s", err)
+               return
+       }
+
+       // If we exit this function due to an error, close the connection.
+       defer func() {
+               if err != nil {
+                       sdr.Close()
+               }
+       }()
+
+       err = binary.Read(sdr.TCPConn, binary.BigEndian, &sdr.Info)
+       if err != nil {
+               err = fmt.Errorf("error reading dongle information: %s", err)
+               return
+       }
+
+       if !sdr.Info.Valid() {
+               err = fmt.Errorf("invalid magic number: expected %q received 
%q", magic, sdr.Info.Magic)
+       }
+
+       return
+}
+
+// Contains the Magic number, tuner information and the number of valid gain 
values.
+type Info struct {
+       Magic     [4]byte
+       Tuner     Tuner
+       GainCount uint32 // Useful for setting gain by index
+}
+
+func (d Info) String() string {
+       return fmt.Sprintf("SDR{Magic:%q Tuner:%s GainCount:%d}", d.Magic, 
d.Tuner, d.GainCount)
+}
+
+// Checks that the magic number received matches the expected byte string 
'RTL0'.
+func (d Info) Valid() bool {
+       return d.Magic == magic
+}
+
+// Provides mapping of tuner value to tuner string.
+type Tuner uint32
+
+func (t Tuner) String() string {
+       switch t {
+       case 1:
+               return "E4000"
+       case 2:
+               return "FC0012"
+       case 3:
+               return "FC0013"
+       case 4:
+               return "FC2580"
+       case 5:
+               return "R820T"
+       case 6:
+               return "R828D"
+       }
+       return "UNKNOWN"
+}
+
+func (sdr SDR) execute(cmd command) (err error) {
+       return binary.Write(sdr.TCPConn, binary.BigEndian, cmd)
+}
+
+type command struct {
+       Command   uint8
+       Parameter uint32
+}
+
+// Command constants defined in rtl_tcp.c
+const (
+       centerFreq     = 0x01
+       sampleRate     = 0x02
+       tunerGainMode  = 0x03
+       tunerGain      = 0x04
+       freqCorrection = 0x05
+       tunerIfGain    = 0x06
+       testMode       = 0x07
+       agcMode        = 0x08
+       directSampling = 0x09
+       offsetTuning   = 0x0A
+       rtlXtalFreq    = 0x0B
+       tunerXtalFreq  = 0x0C
+       gainByIndex    = 0x0D
+       biasTee        = 0x0E
+)
+
+// Set the center frequency in Hz.
+func (sdr SDR) SetCenterFreq(freq uint32) (err error) {
+       return sdr.execute(command{centerFreq, freq})
+}
+
+// Set the sample rate in Hz.
+func (sdr SDR) SetSampleRate(rate uint32) (err error) {
+       return sdr.execute(command{sampleRate, rate})
+}
+
+// Set gain in tenths of dB. (197 => 19.7dB)
+func (sdr SDR) SetGain(gain uint32) (err error) {
+       return sdr.execute(command{tunerGain, gain})
+}
+
+// Set gain mode, true enables manual gain and false enables automatic gain.
+func (sdr SDR) SetGainMode(manual bool) (err error) {
+       if manual {
+               return sdr.execute(command{tunerGainMode, 1})
+       }
+       return sdr.execute(command{tunerGainMode, 0})
+}
+
+// Set gain by index, must be < DongleInfo.GainCount.
+func (sdr SDR) SetGainByIndex(idx uint32) (err error) {
+       if idx >= sdr.Info.GainCount {
+               return fmt.Errorf("invalid gain index: %d", idx)
+       }
+       return sdr.execute(command{gainByIndex, idx})
+}
+
+// Set frequency correction in ppm.
+func (sdr SDR) SetFreqCorrection(ppm int32) (err error) {
+       return sdr.execute(command{freqCorrection, uint32(ppm)})
+}
+
+// Set tuner intermediate frequency stage and gain in tenths of a dB (-30 
means -3.0dB).
+func (sdr SDR) SetTunerIfGain(stage, gain int16) (err error) {
+       return sdr.execute(command{tunerIfGain, (uint32(stage) << 16) | 
uint32(uint16(gain))})
+}
+
+// Set test mode, true for enabled.
+func (sdr SDR) SetTestMode(state bool) (err error) {
+       if state {
+               return sdr.execute(command{testMode, 1})
+       }
+       return sdr.execute(command{testMode, 0})
+}
+
+// Set RTL AGC mode, true for enabled.
+func (sdr SDR) SetAGCMode(state bool) (err error) {
+       if state {
+               return sdr.execute(command{agcMode, 1})
+       }
+       return sdr.execute(command{agcMode, 0})
+}
+
+// Set direct sampling mode.
+func (sdr SDR) SetDirectSampling(state bool) (err error) {
+       if state {
+               return sdr.execute(command{directSampling, 1})
+       }
+       return sdr.execute(command{directSampling, 0})
+}
+
+// Set offset tuning, true for enabled.
+func (sdr SDR) SetOffsetTuning(state bool) (err error) {
+       if state {
+               return sdr.execute(command{offsetTuning, 1})
+       }
+       return sdr.execute(command{offsetTuning, 0})
+}
+
+// Set bias tee, true for enabled.
+func (sdr SDR) SetBiasTee(state bool) (err error) {
+       if state {
+               return sdr.execute(command{biasTee, 1})
+       }
+       return sdr.execute(command{biasTee, 0})
+}
+
+// Set RTL xtal frequency.
+func (sdr SDR) SetRTLXtalFreq(freq uint32) (err error) {
+       return sdr.execute(command{rtlXtalFreq, freq})
+}
+
+// Set tuner xtal frequency.
+func (sdr SDR) SetTunerXtalFreq(freq uint32) (err error) {
+       return sdr.execute(command{tunerXtalFreq, freq})
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/rtlamr-0.9.5/vendor/github.com/bemasher/rtltcp/si/si.go 
new/rtlamr-0.9.6/vendor/github.com/bemasher/rtltcp/si/si.go
--- old/rtlamr-0.9.5/vendor/github.com/bemasher/rtltcp/si/si.go 1970-01-01 
01:00:00.000000000 +0100
+++ new/rtlamr-0.9.6/vendor/github.com/bemasher/rtltcp/si/si.go 2026-10-31 
23:50:59.000000000 +0100
@@ -0,0 +1,99 @@
+package si
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+       "unicode"
+)
+
+const (
+       Y = 1e24
+       Z = 1e21
+       E = 1e18
+       P = 1e15
+       T = 1e12
+       G = 1e9
+       M = 1e6
+       k = 1e3
+       m = 1e-3
+       u = 1e-6
+       n = 1e-9
+       p = 1e-12
+       f = 1e-15
+       a = 1e-18
+       z = 1e-21
+       y = 1e-24
+)
+
+type ScientificNotation float64
+
+func (si ScientificNotation) String() (s string) {
+       return strconv.FormatFloat(float64(si), 'g', -1, 64)
+}
+
+func (si *ScientificNotation) Set(value string) error {
+       // Trim all non-digits from the right, to remove suffix.
+       mantissaStr := strings.TrimRightFunc(value, func(r rune) bool {
+               return !unicode.IsDigit(r)
+       })
+
+       // Recover the suffix by trimming the prefix.
+       suffix := strings.TrimPrefix(value, mantissaStr)
+
+       // Parse mantissa.
+       mantissa, err := strconv.ParseFloat(mantissaStr, 64)
+       if err != nil {
+               return fmt.Errorf("strconv.ParseFloat: %w", err)
+       }
+
+       // Set value.
+       *si = ScientificNotation(mantissa)
+
+       // Apply suffix.
+       for i, s := range suffix {
+               // Ignore everything after first suffix.
+               if i > 0 {
+                       return nil
+               }
+
+               switch s {
+               case 'Y':
+                       *si *= Y
+               case 'Z':
+                       *si *= Z
+               case 'E':
+                       *si *= E
+               case 'P':
+                       *si *= P
+               case 'T':
+                       *si *= T
+               case 'G':
+                       *si *= G
+               case 'M':
+                       *si *= M
+               case 'k':
+                       *si *= k
+               case 'm':
+                       *si *= m
+               case 'u':
+                       *si *= u
+               case 'n':
+                       *si *= n
+               case 'p':
+                       *si *= p
+               case 'f':
+                       *si *= f
+               case 'a':
+                       *si *= a
+               case 'z':
+                       *si *= z
+               case 'y':
+                       *si *= y
+               default:
+                       return fmt.Errorf("invalid suffix: %q", suffix)
+               }
+       }
+
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtlamr-0.9.5/vendor/modules.txt 
new/rtlamr-0.9.6/vendor/modules.txt
--- old/rtlamr-0.9.5/vendor/modules.txt 1970-01-01 01:00:00.000000000 +0100
+++ new/rtlamr-0.9.6/vendor/modules.txt 2026-10-31 23:50:59.000000000 +0100
@@ -0,0 +1,4 @@
+# github.com/bemasher/rtltcp v0.0.0-20261031224328-8792921d55f4
+## explicit; go 1.13
+github.com/bemasher/rtltcp
+github.com/bemasher/rtltcp/si

++++++ vendor.tar.gz ++++++
++++ 2620 lines of diff (skipped)

Reply via email to