sdr

package module
v0.0.0-...-9809d57 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2023 License: MIT Imports: 13 Imported by: 6

README

hz.tools/sdr

⚠ Please read Expectations within this Organization before using it.

Go Reference Go Report Card

Package sdr contains go fundamental types and helpers to allow for reading from and writing to software defined radios.

The interfaces and functions exposed here are designed to mirror and behave in a way that is expected and not surprising to a Go developer. A lot of the design here is taken from the Go io package. A new set of interfaces are required in order to provide a set of tools to work with reading and writing IQ samples.

SDR Format RX/TX State
rtl u8 RX Good
HackRF i8 RX/TX Good
PlutoSDR i16 RX/TX Good
rtl kerberos u8 RX Good
uhd i16/c64/i8 RX/TX Good
airspyhf c64 RX Exp

Toggles for building hz.tools/sdr.

Build Flag Supported Description
sdr.nosimd yes Build without any SIMD ASM (useful for older CPUs)
sdr.nortl yes Build without any RTL-SDR support
sdr.rtl.old yes Disable new API surface support for compat
sdr.nohackrf yes Build without any HackRF support
sdr.nopluto yes Build without any Pluto / iio support
sdr.nouhd yes Build without any UHD support
sdr.noairspyhf yes Build without any AirspyHF+ Support
static yes Internally prepare for a static build
-tags=static

When building a static binary, the -tags=static flag will pass a --static flag to pkg-config to get all the right dependencies to build the .a rather than the .so into the binary. However, due to the LDFLAGS restrictions, we can't pass the other magic required to actually build a static binary yet. For that, you'd have to invoke the build similar to:

$ go build --ldflags='-extldflags "-static"' -tags=static .

Documentation

Overview

Package sdr contains go fundamental types and helpers to allow for reading from and writing to software defined radios.

The interfaces and functions exposed here are designed to mirror and behave in a way that is expected and not supprising to a Go developer. A lot of the design here is taken from the Go io package. A new set of interfaces are required in order to provide a set of tools to work with reading and writing IQ samples.

Since conversion between IQ formats is very expensive, this package operates on a generic "sdr.Samples" type, which is a vector of IQ samples, in some format. Reading and writing time sensitive IQ data should usually be kept in its native format, and allow for a non-real-time conversion on a separate thread to take place.

Most code designed to create and consume IQ data likely wants data in the complex64 format, so an explicit cast or conversion should be made before doing signal processing.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrSampleFormatMismatch will be returned when there's a mismatch between
	// sample formats.
	ErrSampleFormatMismatch = fmt.Errorf("sdr: iq sample formats do not match")

	// ErrSampleFormatUnknown will be returned when a specific iq format is not
	// implemented.
	ErrSampleFormatUnknown = fmt.Errorf("sdr: iq sample format is not understood")

	// ErrDstTooSmall will be returned when attempting to perform an operation
	// and the target buffer is too small to use.
	ErrDstTooSmall = fmt.Errorf("sdr: destination sample buffer is too small")
)
View Source
var (
	// ErrShortBuffer will return if the number of bytes read was less than the
	// minimum required by the callee.
	ErrShortBuffer = fmt.Errorf("sdr: short read")

	// ErrUnexpectedEOF will return if the EOF was reached before parsing was
	// completed.
	ErrUnexpectedEOF = fmt.Errorf("sdr: expected EOF")
)
View Source
var (
	// ErrConversionNotImplemented will be returned if the Sample type is
	// unable to be converted into the desired target format.
	ErrConversionNotImplemented = fmt.Errorf("sdr: unknown format conversion")
)
View Source
var ErrNotSupported = fmt.Errorf("sdr: feature not supported by this device")

ErrNotSupported will be returned when an SDR does not support the feature requested.

View Source
var (
	// ErrPipeClosed will be returned when the Pipe is closed.
	ErrPipeClosed = fmt.Errorf("sdr: pipe is closed")
)
View Source
var ErrShortWrite = errors.New("sdr: short write")

ErrShortWrite will be returned when a write was aborted halfway through.

Functions

func ConvertBuffer

func ConvertBuffer(dst, src Samples) (int, error)

ConvertBuffer the provided Samples to the desired output format.

The conversion will happen in CPU, and this format can be a little slow, but it will get the job done, and beats the heck out of having to worry about the underlying data format.

In the event that the desired format is the same as the provided format this function will copy the source samples to the target buffer.

func Copy

func Copy(dst Writer, src Reader) (int64, error)

Copy will copy samples from the src sdr.Reader to the dst sdr.Writer.

The Reader and Writer must be of the same SampleFormat. If not, that will return an error, and the caller should explicitly define how and where to convert the two formats.

func CopyBuffer

func CopyBuffer(dst Writer, src Reader, buf Samples) (int64, error)

CopyBuffer will copy samples from the src sdr.Reader to the dst sdr.Writer using the provided Buffer.

func CopySamples

func CopySamples(dst, src Samples) (int, error)

CopySamples is the interface version of `copy`, which is type-aware.

This is used when you want to copy samples between two buffers of the same type. This can't be used for conversion.

func Duration

func Duration(s Samples, sampleRate uint) time.Duration

Duration will return the amount of time represented by a slice of Samples.

func LookupTableIndexI8

func LookupTableIndexI8(v [2]int8) uint16

LookupTableIndexI8 will return the index into the LookupTable for an int8 iq sample.

func LookupTableIndexU8

func LookupTableIndexU8(v [2]uint8) uint16

LookupTableIndexU8 will return the index into the LookupTable for an uint8 iq sample.

func MustUnsafeSamplesAsBytes

func MustUnsafeSamplesAsBytes(buf Samples) []byte

MustUnsafeSamplesAsBytes will call the very dangerous UnsafeSamplesAsBytes function, and add even more unsafe and dangerous behavior on top -- namely, a panic if the Samples type can not be represented as a byte slice.

Use of the function should be *seriously* discouraged and not used to every extent possible.

func Pipe

func Pipe(samplesPerSecond uint, format SampleFormat) (PipeReader, PipeWriter)

Pipe will create a new sdr.Reader and sdr.Writer that will allow writes to pass through and show up to a reader. This allows "patching" a Write endpoint into a "Read" endpoint.

func PipeWithContext

func PipeWithContext(
	ctx context.Context,
	samplesPerSecond uint,
	format SampleFormat,
) (PipeReader, PipeWriter)

PipeWithContext will create a new sdr.Reader and sdr.Writer as returned by the Pipe call, but with a custom Context. This is purely used for external control of the lifecycle of the Pipe.

func ReadAtLeast

func ReadAtLeast(r Reader, buf Samples, min int) (int, error)

ReadAtLeast reads from r into buf until it has read at least min bytes.

func ReadFull

func ReadFull(r Reader, buf Samples) (int, error)

ReadFull reads exactly len(buf) bytes from r into buf.

func SetGainStages

func SetGainStages(device Sdr, gainSettings map[string]float32) error

SetGainStages will set the GainStages on the device by their Name as returned by .String().

func UnsafeSamplesAsBytes

func UnsafeSamplesAsBytes(buf Samples) ([]byte, error)

UnsafeSamplesAsBytes is a very dangerous function.

Use of the function should be *seriously* discouraged and not used to every extent possible. This is only to be used at carefully controlled boundaries where safe high-level primitives can't be used for a serious technical purpose.

Types

type Closer

type Closer interface {
	Close() error
}

Closer is the interface that wraps the basic Close method.

type GainStage

type GainStage interface {
	// GainRange expresses the max and minimum values that this Gain stage
	// can be set to. The value may be negative in the case of attenuation,
	// or positive in the case of amplification.
	Range() [2]float32

	// Type will return what type of GainStage this is, usually what part
	// of the chain from antenna to USB the gain stage exists within.
	Type() GainStageType

	// String will return a human readable name to be used when referencing this
	// stage to a user. This string should match the format "%s Gain Stage" in
	// a sensible way, something like "IF", "LNA" or "Baseband" are all good
	// examples.
	String() string
}

GainStage is a step at which an adjustment can be made to the values that flow through that stage.

type GainStageType

type GainStageType uint16

GainStageType describes what type the GainStage is.

This is mostly centered around where in the chain from antenna to USB this particular GainStage is, as well as if it's on the rx or tx side.

const (
	// GainStageTypeUnknown is provided when the GainStageType is not known.
	GainStageTypeUnknown GainStageType = 0x0000

	// GainStageTypeIF represents a GainStage where Gain is applied to the
	// signal in its Intermediate Frequency stage.
	GainStageTypeIF GainStageType = 0x0001

	// GainStageTypeBB represents a GainStage where the Gain is applied at
	// the Baseband.
	GainStageTypeBB GainStageType = 0x0002

	// GainStageTypeFE represents a GainStage before the baseband/if, in
	// the radio Frontend
	GainStageTypeFE GainStageType = 0x0004

	// GainStageTypeAmp represents a GainStage before the baseband/if, in
	// the radio Frontend
	GainStageTypeAmp GainStageType = 0x0008

	// GainStageTypeAttenuator represents a GainStage which reduces the power
	// passing through rather than amplifying it.
	GainStageTypeAttenuator GainStageType = 0x0010

	// GainStageTypeRecieve represents a GainStage on the receive path.
	GainStageTypeRecieve GainStageType = 0x0100

	// GainStageTypeTransmit represents a GainStage on the transmit path.
	GainStageTypeTransmit GainStageType = 0x0200
)

func (GainStageType) Is

func (gst GainStageType) Is(gainStageType GainStageType) bool

Is will check to see if the GainStageType is another GainStageType. Direct comparison won't work, since we bitmask the two together, so this is a helpful function to do an XOR and ==.

func (GainStageType) String

func (gst GainStageType) String() string

String will return a short human-readable list of type names.

type GainStages

type GainStages []GainStage

GainStages is a list of GainStage objects.

func (GainStages) Filter

func (gs GainStages) Filter(gainStageType GainStageType) GainStages

Filter will return the GainStages that match a specific criteria.

func (GainStages) First

func (gs GainStages) First(gainStageType GainStageType) GainStage

First will return the first GainStage that matches the criteria, returning nil if no such gain stage exists.

func (GainStages) Map

func (gs GainStages) Map() map[string]GainStage

Map will return the GainStages in a map, where the key is the value returned by .String().

type HardwareInfo

type HardwareInfo struct {
	// Manufacturer is the person, company or group that created this SDR.
	Manufacturer string

	// Product is the name of the specific SDR product connected.
	Product string

	// Serial is an identifier that is unique to the connected SDR.
	Serial string
}

HardwareInfo contains information about the connected SDR.

Some subset of this information may be populated, none of it is a hard requirement if it does not exist. Not all SDRs will have a Serial, for example.

type LookupTable

type LookupTable interface {
	// Lookup - uncreatively - looks up the values from the source ('src')
	// IQ buffer, and writes the precomputed value to the destination ('dst')
	// buffer.
	//
	// 'dst' and 'src' MUST match the configured sample format(s).
	Lookup(dst, src Samples) (int, error)

	// SourceSampleFormat is the sample format of the precomputed table keys.
	// this must be one of SampleFormatI8 or SampleFormatU8, depending on the
	// configuration of the LookupTable.
	SourceSampleFormat() SampleFormat

	// DestinationSampleFormat is the sample format of the precomputed table
	// values. This can be any IQ type.
	DestinationSampleFormat() SampleFormat
}

LookupTable or "iq table" is a micro-optimization for extremely hotpath code where you make a memory / one-time CPU tradeoff for many expensive operations on an int8 or uint8. Since both are [2]int8 or [2]uint8, it's very possible to pre-compute all possible input IQ samples, since both could "just" be thought of as an int16, which is less than a fraction of a second to precompute (in IQ terms - 65535 (int16 max) is only 0.03 of a second at 2Msps. That being said -- this isn't free and shouldn't be overused.

func NewLookupTable

func NewLookupTable(inputFormat SampleFormat, lookup Samples) (LookupTable, error)

NewLookupTable will create a new LookupTable. The 'inputFormat' is the format of input IQ samples. This must be either SampleFormatI8 or SampleFormatU8.

On the other end, the 'lookup' buffer is the data to place into the output buffer depending on the input samples. The 'lookup' buffer must be exactly 65536 samples long.

type PipeReadWriter

type PipeReadWriter interface {
	PipeReader
	PipeWriter
}

PipeReadWriter is the Read/Write interface exposed by a Pipe.

type PipeReader

type PipeReader interface {
	ReadCloser

	// CloseWithError will Close the pipe with a specific Error rather than
	// the default ErrPipeClosed. This can be useful if code is expecting an
	// io.EOF, for instance.
	CloseWithError(error) error
}

PipeReader is the Read interface exposed by the Pipe.

type PipeWriter

type PipeWriter interface {
	WriteCloser

	// CloseWithError will Close the pipe with a specific Error rather than
	// the default ErrPipeClosed. This can be useful if code is expecting an
	// io.EOF, for instance.
	CloseWithError(error) error
}

PipeWriter is the Write interface exposed by the Pipe.

type ReadCloser

type ReadCloser interface {
	Reader
	Closer
}

ReadCloser is the interface that groups the basic Read and Close methods.

func ReaderWithCloser

func ReaderWithCloser(r Reader, c func() error) ReadCloser

ReaderWithCloser will add a closer to a reader to make an sdr.ReadCloser

type ReadClosers

type ReadClosers []ReadCloser

ReadClosers is a collection of ReadCloser objects.

func (ReadClosers) Close

func (rcs ReadClosers) Close() error

Close will close all the ReadClosers.

func (ReadClosers) Readers

func (rcs ReadClosers) Readers() Readers

Readers will return the ReadClosers as a Reader slice.

func (ReadClosers) SampleFormat

func (rcs ReadClosers) SampleFormat() SampleFormat

SampleFormat returns the IQ format of the Readers.

func (ReadClosers) SampleRate

func (rcs ReadClosers) SampleRate() uint

SampleRate returns the number of IQ samples per second.

type ReadWriteCloser

type ReadWriteCloser interface {
	Reader
	Writer
	Closer
}

ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.

type Reader

type Reader interface {
	// Read IQ Samples into the target Samples buffer. There are two return
	// values, an int representing the **IQ** samples (not bytes) read by this
	// function, and any error conditions encountered.
	Read(Samples) (int, error)

	// Get the sdr.SampleFormat
	SampleFormat() SampleFormat

	// SampleRate will get the number of samples per second that this
	// stream is communicating at.
	SampleRate() uint
}

Reader is the interface that wraps the basic Read method.

func ByteReader

func ByteReader(
	r io.Reader,
	byteOrder binary.ByteOrder,
	samplesPerSecond uint,
	sf SampleFormat,
) Reader

ByteReader will wrap an io.Reader, and decode encoded IQ data from raw bytes into an sdr.Samples object.

func MultiReader

func MultiReader(readers ...Reader) (Reader, error)

MultiReader will act like `cat`, passing Reads through from one reader to the next until the end of the streams.

An io.EOF will be returned if they all return EOF, otherwise the first error to be hit will be returned.

func TeeReader

func TeeReader(r Reader, w Writer) (Reader, error)

TeeReader will write all values read from sdr.Reader `r` to the sdr.Writer `w`. There is no buffering or storage, writes will block the returned Read.

type Readers

type Readers []Reader

Readers represents a collection of Readers.

func (Readers) SampleFormat

func (rs Readers) SampleFormat() SampleFormat

SampleFormat returns the IQ Format of the Readers.

func (Readers) SampleRate

func (rs Readers) SampleRate() uint

SampleRate returns the number of samples per second in the stream.

func (Readers) Wrap

func (rs Readers) Wrap(fn func(Reader) Reader) Readers

Wrap will apply the function 'fn' to each Reader in this collection, and return a new slice of those new Reader objects.

func (Readers) WrapErr

func (rs Readers) WrapErr(fn func(Reader) (Reader, error)) (Readers, error)

WrapErr will apply the function 'fn' to each Reader in this collection, and return a new slice of those new Reader objects. If any error is encountered, that error is returned.

type Receiver

type Receiver interface {
	Sdr

	// StartRx will listen on the configured frequency and start to stream iq
	// samples to be read out of the provided Reader. It's absolutely
	// imperative that the consuming code will actively consume from the
	// Reader, or backlogged samples can result in dropped samples or other
	// error conditions. Those error conditions are not defined at this time,
	// but may break in wildly unpredictable ways, since the time sensitive
	// SDR code may hang waiting for reads.
	StartRx() (ReadCloser, error)
}

Receiver is an "extension" of the SDR Interface, it contains all the common control methods, plus additional bits to receive iq data from the airwaves.

This can either be used as part of a function signature if your code really only needs to receive, or as part of a type-cast to determine if the SDR is capable of receiving.

type SampleFormat

type SampleFormat uint8

SampleFormat is an ID used throughout go-rf to uniquely identify what type the IQ samples are in. This allows code to quickly compare to see if two types are talking about the same type or not, without resoring to expensive read operations and type casting.

const (
	// SampleFormatC64 indicates that SamplesC64 will be handled. See
	// sdr.SamplesC64 for more information.
	SampleFormatC64 SampleFormat = 1

	// SampleFormatU8 indicates that SamplesU8 will be handled. See
	// sdr.SamplesU8 for more information.
	SampleFormatU8 SampleFormat = 2

	// SampleFormatI16 indicates that SamplesI16 will be handled. See
	// sdr.SamplesI16 for more information.
	SampleFormatI16 SampleFormat = 3

	// SampleFormatI8 indicates that SamplesI8 will be handled. See
	// sdr.SamplesI8 for more information.
	SampleFormatI8 SampleFormat = 4
)

func (SampleFormat) Size

func (sf SampleFormat) Size() int

Size will return the number of bytes that are needed to represent a single phasor, both real and imaginary.

func (SampleFormat) String

func (sf SampleFormat) String() string

String returns the format name as a human readable String.

type Samples

type Samples interface {
	// Format returns the type of this vector, as exported by the SampleFormat
	// enum.
	Format() SampleFormat

	// Size will return the size of this sdr.Samples in *bytes*. This is used
	// when your code needs to be aware of the underlying storage size. This
	// should usually only be used at i/o boundaries.
	Size() int

	// Length will return the number of IQ samples in this vector of Samples.
	//
	// This is the count of real and imaginary pairs, so in the case
	// of the U8 type, this will be half the size of the vector.
	//
	// This function is usually the correct one to use when processing
	// sample information.
	Length() int

	// Slice will return a slice of the sample buffer from the provided
	// starting position until the ending position. The returned value is
	// assumed to be a slice, which is to say, mutations of the returned
	// Samples will modify the slice from whence it came.
	//
	// samples.Slice(0, 10) is assumed to be the same as samples[:10], except
	// it does not require the typecast to the concrete type implementing
	// this interface.
	Slice(int, int) Samples
}

Samples represents a vector of IQ data.

This type is an interface_ and not a struct or typedef to complex64 because this allows the generic IQ helpers in this package to operate on the native format of the SDR without requiring expensive conversions to other types.

This package contains 4 Samples implementations:

  • SamplesU8 - interleaved uint8 values
  • SamplesI8 - interleaved int8 values
  • SamplesI16 - interleaved int16 values
  • SamplesC64 - vector of complex64 values (interleaved float32 values)

This should cover most common SDRs, but if you're handing a type of IQ data that is not supported, you may either implement the Samples type yourself along with the required interface points, convert to a format this library supports, or send a PR to this library adding support to this format.

func MakeSamples

func MakeSamples(sampleFormat SampleFormat, sampleSize int) (Samples, error)

MakeSamples will create a buffer of a specified size and type. This will return a newly allocated slice of Samples. This function is used when the code processing Samples is fairly generic, to avoid switches on type, and to reduce the cost of adding new sample formats.

If your code is specific to a given Sample format, for instance if your code only supports SamplesC64, it's fine to not use this function at all.

type SamplesC64

type SamplesC64 []complex64

SamplesC64 indicates that the samples are in a complex64 number, which is itself two interleaved float32 numbers, the i and q value. In memory, this is the same thing as interleaving i and q values in a float array.

This is the format that is most useful to process iq data in from a mathematical perspective, and is going to be the most common type to work with when writing signal processing code.

func (SamplesC64) Add

func (s SamplesC64) Add(c []complex64) error

Add will conduct a complex addition of each phasor in this buffer by a provided complex number 'c', writing results to 'dst'.

func (SamplesC64) Format

func (s SamplesC64) Format() SampleFormat

Format returns the type of this vector, as exported by the SampleFormat enum.

func (SamplesC64) Length

func (s SamplesC64) Length() int

Length will return the number of IQ samples in this vector of Samples.

This is the count of real and imaginary pairs, so in the case of the U8 type, this will be half the size of the vector.

This function is usually the correct one to use when processing sample information.

func (SamplesC64) Multiply

func (s SamplesC64) Multiply(c complex64)

Multiply will conduct a complex multiplication of each phasor in this buffer by a provided complex number 'c'.

func (SamplesC64) Scale

func (s SamplesC64) Scale(r float32)

Scale will multiply each I and Q value by the provided real value 'r'. This will *not* do a complex multiplication, this is the same as if each phasor had their real and imag parts multiplied by the provided real value 'r'.

func (SamplesC64) Size

func (s SamplesC64) Size() int

Size will return the size of this sdr.Samples in *bytes*. This is used when your code needs to be aware of the underlying storage size. This should usually only be used at i/o boundaries.

func (SamplesC64) Slice

func (s SamplesC64) Slice(start, end int) Samples

Slice will return a slice of the sample buffer from the provided starting position until the ending position. The returned value is assumed to be a slice, which is to say, mutations of the returned Samples will modify the slice from whence it came.

samples.Slice(0, 10) is assumed to be the same as samples[:10], except it does not require the typecast to the concrete type implementing this interface.

func (SamplesC64) ToI16

func (s SamplesC64) ToI16(out SamplesI16) (int, error)

ToI16 will convert the complex64 data to int16 data.

func (SamplesC64) ToI8

func (s SamplesC64) ToI8(out SamplesI8) (int, error)

ToI8 will convert the complex64 data to int8 data.

func (SamplesC64) ToU8

func (s SamplesC64) ToU8(out SamplesU8) (int, error)

ToU8 will convert the Complex data to a vector of interleaved uint8s.

type SamplesI16

type SamplesI16 [][2]int16

SamplesI16 indicates that the samples are being sent as a vector of interleaved int16 integers. The values range from +32767 to -32768. 0 remains, well 0.

There are a few hazards for people working with this type at an IO boundary with an SDR (if you're consuming this data, you don't really have to care much - it's a number! numbers are great! Enjoy!).

Firstly, this type isn't really any particular device's native format, which may cause a bit of confusion, but it's close enough to be useful when reading from ADCs that have 12 or 14 bits of precision, since you don't want to align to non-8-bit boundaries while also maintaining sanity. As a result, working with this type can be a bit awkward when doing IO with an SDR, since you either have to align to the MSB or LSB. Some SDRs (I'm looking at you, PlutoSDR) expect their samples to be in both formats depending on direction.

If you are at the IO boundary with an SDR the correct format for this type is MSB aligned data. As soon as you convert the byte stream into this type, be sure it's immediately MSB aligned. If your SDR is giving you LSB aligned data, you may need to call ShiftLSBToMSBBits with the number of bits your data is in (for instance, `12` for a 12 bit ADC) to MSB align.

func (SamplesI16) Format

func (s SamplesI16) Format() SampleFormat

Format returns the type of this vector, as exported by the SampleFormat enum.

func (SamplesI16) Length

func (s SamplesI16) Length() int

Length will return the number of IQ samples in this vector of Samples.

This is the count of real and imaginary pairs, so in the case of the U8 type, this will be half the size of the vector.

This function is usually the correct one to use when processing sample information.

func (SamplesI16) ShiftLSBToMSBBits

func (s SamplesI16) ShiftLSBToMSBBits(bits int)

ShiftLSBToMSBBits is a helper function to be used when the input data is not actually 16 bits. This is usually fine if the data is MSB aligned, since the range is still roughly the int16 max / min (just like, 8 off). However, if the data is LSB aligned, this is a major shitshow, since the max is no longer 2**16, the max is 2**12 or 2**14. Rather than make multiple sdr.Sample types for each ADC bit count, the SDR code should call ShiftLSBToMSBBits at the boundary to shift the data from LSB to MSB aligned to get full-range values.

The value `bits` is the number of bits the ADC sends. This will result in a bitshift of 16 - bits. So, if you have a 12 bit ADC, and this is invoked, each I and Q sample will be shifted left 4 bits, bringing the range from +/- 2047 to +/-32768.

This will mutate the buffer in place.

func (SamplesI16) Size

func (s SamplesI16) Size() int

Size will return the size of this sdr.Samples in *bytes*. This is used when your code needs to be aware of the underlying storage size. This should usually only be used at i/o boundaries.

func (SamplesI16) Slice

func (s SamplesI16) Slice(start, end int) Samples

Slice will return a slice of the sample buffer from the provided starting position until the ending position. The returned value is assumed to be a slice, which is to say, mutations of the returned Samples will modify the slice from whence it came.

samples.Slice(0, 10) is assumed to be the same as samples[:10], except it does not require the typecast to the concrete type implementing this interface.

func (SamplesI16) ToC64

func (s SamplesI16) ToC64(out SamplesC64) (int, error)

ToC64 will convert the int16 data to a vector of complex64 numbers.

func (SamplesI16) ToI8

func (s SamplesI16) ToI8(out SamplesI8) (int, error)

ToI8 will convert the int16 data to interleaved int8 bit samples.

func (SamplesI16) ToU8

func (s SamplesI16) ToU8(out SamplesU8) (int, error)

ToU8 will convert the int16 data to interleaved uint8 bit samples. This looks a lot like a (weirdly) simplified version of c64 -> u8 since both have to deal with shifting from negative.

type SamplesI8

type SamplesI8 [][2]int8

SamplesI8 indicates that the samples are being sent as a vector of interleaved int8 numbers, where -128 is -1, and 1 is 127.

This is the native format of the HackRF

func LookupTableIdentityI8

func LookupTableIdentityI8() SamplesI8

LookupTableIdentityI8 will return an identity SamplesI8 table, from 0 to max in index order. This can be used to apply a transform to (like Add, or Multiply).

func (SamplesI8) Format

func (s SamplesI8) Format() SampleFormat

Format returns the type of this vector, as exported by the SampleFormat enum.

func (SamplesI8) Length

func (s SamplesI8) Length() int

Length will return the number of IQ samples in this vector of Samples.

This is the count of real and imaginary pairs, so in the case of the I8 type, this will be half the size of the vector.

This function is usually the correct one to use when processing sample information.

func (SamplesI8) Size

func (s SamplesI8) Size() int

Size will return the size of this sdr.Samples in *bytes*. This is used when your code needs to be aware of the underlying storage size. This should usually only be used at i/o boundaries.

func (SamplesI8) Slice

func (s SamplesI8) Slice(start, end int) Samples

Slice will return a slice of the sample buffer from the provided starting position until the ending position. The returned value is assumed to be a slice, which is to say, mutations of the returned Samples will modify the slice from whence it came.

samples.Slice(0, 10) is assumed to be the same as samples[:10], except it does not require the typecast to the concrete type implementing this interface.

func (SamplesI8) ToC64

func (s SamplesI8) ToC64(out SamplesC64) (int, error)

ToC64 will convert the int8 data to a vector of complex64 numbers.

func (SamplesI8) ToI16

func (s SamplesI8) ToI16(out SamplesI16) (int, error)

ToI16 will convert the int8 data to a vector of interleaved int16 values.

func (SamplesI8) ToU8

func (s SamplesI8) ToU8(out SamplesU8) (int, error)

ToU8 will convert the int8 data to a vector of interleaved uint8

type SamplesPool

type SamplesPool struct {
	// contains filtered or unexported fields
}

SamplesPool creates a dynamically sized buffer pool of a set size and sample format. This allows code to reuse buffers, and avoid allocations if a buffer is ready for use.

Under the hood this is a sync.Pool, but with some type-safe (well, as type safe as you can get by returning another interface type...) hooks to make this a bit more ergonomic to use.

func NewSamplesPool

func NewSamplesPool(format SampleFormat, length int) (*SamplesPool, error)

NewSamplesPool will create a new SamplesPool that creates buffers of the provided sample format and length.

Under the hood this is a wrapped sync.Pool.

func (SamplesPool) Get

func (sp SamplesPool) Get() Samples

Get will either return an unused buffer, or allocate a new one for your use.

The smallest size of a buffer returned will be the length passed to the NewSamplesPool constructor, of the provided SampleFormat.

func (SamplesPool) Put

func (sp SamplesPool) Put(s Samples)

Put will return a buffer to the pool. In the future this is likely to panic if the buffer returned is not the same size as the buffer that was taken out, but since the size won't /shrink/, it's actually not that bad to deal with here.

type SamplesU8

type SamplesU8 [][2]uint8

SamplesU8 indicates that the samples are being sent as a vector of interleaved uint8 numbers, where 0 is -1, and 1 is 0xFF.

This type is very hard to process, since the 0 value is 127.5, which is not representable, but it's *very* effective to send data over a connection, since it's the most compact representation.

This is the native format of the rtl-sdr.

func LookupTableIdentityU8

func LookupTableIdentityU8() SamplesU8

LookupTableIdentityU8 will return an identity SamplesU8 table, from 0 to max in index order. This can be used to apply a transform to (like Add, or Multiply).

func (SamplesU8) Format

func (s SamplesU8) Format() SampleFormat

Format returns the type of this vector, as exported by the SampleFormat enum.

func (SamplesU8) Length

func (s SamplesU8) Length() int

Length will return the number of IQ samples in this vector of Samples.

This is the count of real and imaginary pairs, so in the case of the U8 type, this will be half the size of the vector.

This function is usually the correct one to use when processing sample information.

func (SamplesU8) Size

func (s SamplesU8) Size() int

Size will return the size of this sdr.Samples in *bytes*. This is used when your code needs to be aware of the underlying storage size. This should usually only be used at i/o boundaries.

func (SamplesU8) Slice

func (s SamplesU8) Slice(start, end int) Samples

Slice will return a slice of the sample buffer from the provided starting position until the ending position. The returned value is assumed to be a slice, which is to say, mutations of the returned Samples will modify the slice from whence it came.

samples.Slice(0, 10) is assumed to be the same as samples[:10], except it does not require the typecast to the concrete type implementing this interface.

func (SamplesU8) ToC64

func (s SamplesU8) ToC64(out SamplesC64) (int, error)

ToC64 will convert the uint8 data to a vector of complex64 numbers.

func (SamplesU8) ToI16

func (s SamplesU8) ToI16(out SamplesI16) (int, error)

ToI16 will convert the uint8 data to a vector of interleaved int16 values.

func (SamplesU8) ToI8

func (s SamplesU8) ToI8(out SamplesI8) (int, error)

ToI8 will convert the uint8 data to a vector of int8 values.

type Sdr

type Sdr interface {
	// Close will free any resources held by the SDR object, and disconnect
	// from the hardware, if applicable. After this call, it's assumed
	// that any further function calls become very undefined behavior.
	Close() error

	// SetCenterFrequency will set the center of the hardware frequency to a
	// specific frequency in Hz.
	SetCenterFrequency(rf.Hz) error

	// GetCenterFrequency will get the centered hardware frequency, in Hz.
	GetCenterFrequency() (rf.Hz, error)

	// SetAutomaticGain will let the SDR take care of setting the gain as
	// required.
	SetAutomaticGain(bool) error

	// GetGainStages will return all gain stages that are supported by this
	// SDR, sorted in order from the antenna backwards to the USB port.
	GetGainStages() (GainStages, error)

	// GetGain will return the Gain set for the specific Gain stage.
	GetGain(GainStage) (float32, error)

	// SetGain will set the Gain for a specific Gain stage.
	SetGain(GainStage, float32) error

	// SetSampleRate will set the number of samples per second that this
	// device should be sending back to us. A lower number usually gives us less
	// RF bandwidth, and a higher number may result in corruption (in the case
	// of the rtl-sdr) or dropped samples (in the case of the Pluto and friends).
	SetSampleRate(uint) error

	// GetSampleRate will get the number of samples per second that this
	// device is configured to be sending back to us.
	GetSampleRate() (uint, error)

	// SampleFormat returns the type of this vector, as exported by the
	// SampleFormat enum.
	SampleFormat() SampleFormat

	// HardwareInfo will return information about the connected SDR.
	HardwareInfo() HardwareInfo
}

Sdr is the generic interface that all SDRs will expose. Since this covers an extensive amount of functionality, it's expected some devices will not support a given function. If that happens, the error that must be returned is an ErrNotSupported.

A specific SDR may support additional functionality, so be sure to check the documentation of the underlying SDR implementation as well!

type Transceiver

type Transceiver interface {
	Sdr
	Receiver
	Transmitter
}

Transceiver is an "extension" of the SDR Interface, it contains all the common control methods, plus additional bits to both transmit and receive iq data.

This can either be used as part of a function signature if your code really only needs to both receive and transmit, or as part of a type-cast to determine if the SDR is capable of both receiving and transmitting.

type Transmitter

type Transmitter interface {
	Sdr

	// StartTx will begin to transmit on the configured frequency, and start to
	// stream iq samples written to the underlying hardware to be sent over
	// the air.
	//
	// It's absolutely imperative that the producing code feed iq samples into
	// the transmitter at the specified rate, or bad things may happen and
	// cause wildly unpredictable things.
	StartTx() (WriteCloser, error)
}

Transmitter is an "extension" of the SDR Interface, it contains all the common control methods, plus additional bits to transmit iq data over the airwaves.

This can either be used as part of a function signature if your code really only needs to transmit, or as part of a type-cast to determine if the SDR is capable of transmitting.

type WriteCloser

type WriteCloser interface {
	Writer
	Closer
}

WriteCloser is the interface that groups the basic Read and Close methods.

func WriterWithCloser

func WriterWithCloser(w Writer, c func() error) WriteCloser

WriterWithCloser will add a closer to a writer to make an sdr.WriteCloser

type Writer

type Writer interface {
	// Write IQ Samples into the target Samples buffer. There are two return
	// values, an int representing the **IQ** samples (not bytes) read by this
	// function, and any error conditions encountered.
	Write(Samples) (int, error)

	// Get the sdr.SampleFormat
	SampleFormat() SampleFormat

	// SampleRate will get the number of samples per second that this
	// stream is communicating at.
	SampleRate() uint
}

Writer is the interface that wraps the basic Write method.

func ByteWriter

func ByteWriter(
	w io.Writer,
	byteOrder binary.ByteOrder,
	samplesPerSecond uint,
	sf SampleFormat,
) Writer

ByteWriter will wrap an io.Writer, and write encoded IQ data as a series of raw bytes out.

func Discard

func Discard(sampleRate uint, sampleFormat SampleFormat) Writer

Discard will accept writes, and store them safely... nowhere. This is a highly optimized and very fast writer. Just don't expect to get your data back.

func MultiWriter

func MultiWriter(
	writers ...Writer,
) (Writer, error)

MultiWriter creates a writer that duplicates its writes to all the provided writers, similar to the Unix tee(1) command, or io.MultiWriter.

This has the same behavior as an io.MultiWriter, but will copy between IQ streams.

Directories

Path Synopsis
Package airspyhf contains an sdr.Sdr implementation for Airspy SDRs
Package airspyhf contains an sdr.Sdr implementation for Airspy SDRs
Package debug contains helpers which are useful when trying to track down errors or current state from the hz.tools/sdr package and package implementations/drivers.
Package debug contains helpers which are useful when trying to track down errors or current state from the hz.tools/sdr package and package implementations/drivers.
Package fft contains a common interface to perform FFTs between frequency and time-Series complex data.
Package fft contains a common interface to perform FFTs between frequency and time-Series complex data.
Package hackrf contains the HackRF driver for hz.tools/sdr
Package hackrf contains the HackRF driver for hz.tools/sdr
Package internal contains some pointy knobs which are explicitly not part of the external API.
Package internal contains some pointy knobs which are explicitly not part of the external API.
simd
Package simd contains some IQ specific helpers which have both a native go as well as tuned implementation depending on the platform.
Package simd contains some IQ specific helpers which have both a native go as well as tuned implementation depending on the platform.
warning
Package warning contains helpers to mark functions as unsafe, experimental or deprecated.
Package warning contains helpers to mark functions as unsafe, experimental or deprecated.
Package mock contains a sdr.Sdr implementation that is suitable for testing.
Package mock contains a sdr.Sdr implementation that is suitable for testing.
Package pluto contains an sdr.Sdr implementation for the Analog Devices ADALM-PLUTO SDR.
Package pluto contains an sdr.Sdr implementation for the Analog Devices ADALM-PLUTO SDR.
iio
Package iio contains a Go wrapper around libiio, to talk with devices such as the Analog Devices ADALM-PLUTO.
Package iio contains a Go wrapper around libiio, to talk with devices such as the Analog Devices ADALM-PLUTO.
rtl
Package rtl contains an sdr.Sdr implementation for rtlsdr based SDRs.
Package rtl contains an sdr.Sdr implementation for rtlsdr based SDRs.
e4k
Package e4k contains code to translate gains in dB to values to be sent to the e4k's 6-stage IF gain controls.
Package e4k contains code to translate gains in dB to values to be sent to the e4k's 6-stage IF gain controls.
kerberos
Package kerberos contains the KerberosSDR driver for hz.tools/sdr
Package kerberos contains the KerberosSDR driver for hz.tools/sdr
kerberos/internal
Package internal contains kerberos specific internal bits of API.
Package internal contains kerberos specific internal bits of API.
Package rtltcp contains an sdr.Sdr implementation to talk to an rtl_tcp server.
Package rtltcp contains an sdr.Sdr implementation to talk to an rtl_tcp server.
Package stream contains a number of helpers to process sdr.Reader or sdr.Writers.
Package stream contains a number of helpers to process sdr.Reader or sdr.Writers.
Package testutils contains some helpers for testing SDR code and other implementations.
Package testutils contains some helpers for testing SDR code and other implementations.
Package uhd contains the UHD USRP driver for hz.tools/sdr
Package uhd contains the UHD USRP driver for hz.tools/sdr
Package yikes contains helpers which *are* exported for other packages, but the use of these helpers is *strongly* discouraged, and is very unsafe.
Package yikes contains helpers which *are* exported for other packages, but the use of these helpers is *strongly* discouraged, and is very unsafe.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL