Skip to content

Commit

Permalink
Merge pull request #18 from JuliaTelecom/sjk/highlevel3
Browse files Browse the repository at this point in the history
Address several issues discovered in testing
  • Loading branch information
sjkelly committed Sep 8, 2021
2 parents 90c5414 + a991e2b commit 37a832e
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 83 deletions.
8 changes: 4 additions & 4 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ channel.gain = 42u"dB"
channel.sample_rate = 2.1u"MHz"
# Open a (potentially multichannel) stream on this channel
stream = SoapySDR.Stream(ComplexF32, [channel])
stream = SoapySDR.Stream([channel])
SoapySDR.activate!(stream)
# Write out random noise
Expand All @@ -70,9 +70,9 @@ channel.gain = 42u"dB"
channel.sample_rate = 2.1u"MHz"
# Open a (potentially multichannel) stream on this channel
stream = SoapySDR.Stream(ComplexF32, [channel])
stream = SoapySDR.Stream([channel])
SoapySDR.activate!(stream)
# Collect data
Base.read(stream, 10000)
# Collect all available samples in the buffer
Base.read(stream)
```
109 changes: 58 additions & 51 deletions src/highlevel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -256,16 +256,6 @@ function Base.show(io::IO, ::MIME"text/plain", c::Channel)
end
end

"""
native_stream_format(c::Channel)
Returns the format type and fullscale resolution of the native stream.
"""
function native_stream_format(c::Channel)
fmt, fullscale = SoapySDRDevice_getNativeStreamFormat(c.device.ptr, c.direction, c.idx)
_stream_type_soapy2jl[unsafe_string(fmt)], fullscale
end

struct ChannelList <: AbstractVector{Channel}
device::Device
direction::Direction
Expand Down Expand Up @@ -458,52 +448,47 @@ struct FreqSpec{T}
kwargs::Dict{Any, String}
end

### Streams
### Stream Utility Functions

#TODO {T} ?
struct StreamFormat
T
end
function Base.print(io::IO, sf::StreamFormat)
T = sf.T
if T <: Complex
print(io, 'C')
T = real(T)
end
if T <: AbstractFloat
print(io, 'F')
elseif T <: Signed
print(io, 'S')
elseif T <: Unsigned
print(io, 'U')
else
error("Unknown format")
end
print(io, 8*sizeof(T))
end
"""
stream_formats(::Channel)
function StreamFormat(s::String)
if haskey(_stream_type_soapy2jl, s)
T = _stream_type_soapy2jl[s]
return StreamFormat(T)
else
error("Unknown format")
end
end
Returns the stream formats supported by the device.
Note: Since Julia is a multiple dispatch and generic language, it is
preferrable to use `native_stream_format(c::Channel)` for optimal processing throughput.
Only use this function if non-standard formats such as Complex Int12 and Complex Int4
are native to the device and not handled by dispatch on `Complex`.
"""
function stream_formats(c::Channel)
slist = StringList(SoapySDRDevice_getStreamFormats(c.device.ptr, c.direction, c.idx)...)
map(StreamFormat, slist)
map(_stream_map_soapy2jl, slist)
end

# Internal, reflected in Stream.mtu
function mtu(d::Device, stream::Ptr{SoapySDRStream})
SoapySDRDevice_getStreamMTU(d.ptr, stream)
end

"""
native_stream_format(c::Channel) -> Type, fullscale
Returns the format type and fullscale resolution of the native stream.
"""
function native_stream_format(c::Channel)
fmt, fullscale = SoapySDRDevice_getNativeStreamFormat(c.device.ptr, c.direction, c.idx)
_stream_map_soapy2jl(unsafe_string(fmt)), fullscale
end

## Stream

mutable struct Stream{T}
d::Device
nchannels::Int
mtu::Int
ptr::Ptr{SoapySDRStream}
function Stream{T}(d::Device, nchannels::Int, ptr::Ptr{SoapySDRStream}) where {T}
this = new{T}(d, nchannels, ptr)
this = new{T}(d, nchannels, mtu(d, ptr), ptr)
finalizer(SoapySDRDevice_closeStream, this)
return this
end
Expand All @@ -516,14 +501,13 @@ function Base.show(io::IO, s::Stream)
print(io, "Stream on ", s.d.hardware)
end

function Stream(format::Union{StreamFormat, Type}, device::Device, direction::Direction; kwargs...)
format = StreamFormat(format) # TODO isa(format, StreamFormat) ? format : StreamFormat(format)?
function Stream(format::Type, device::Device, direction::Direction; kwargs...)
isempty(kwargs) || error("TODO")
Stream{T}(device, 1, SoapySDRDevice_setupStream(device, direction, string(format), C_NULL, 0, C_NULL))
end

function Stream(format::Union{StreamFormat, Type}, channels::Vector{Channel}; kwargs...)
format = StreamFormat(format) # TODO isa(format, StreamFormat) ? format : StreamFormat(format)?
function Stream(format::Type, channels::Vector{Channel}; kwargs...)
soapy_format = _stream_map_jl2soapy(format)
isempty(kwargs) || error("TODO")
isempty(channels) && error("Must specify at least one channel or use the device/direction constructor for automatic.")
device = first(channels).device
Expand All @@ -533,17 +517,25 @@ function Stream(format::Union{StreamFormat, Type}, channels::Vector{Channel}; kw
end
throw(ArgumentError("Channels must agree on device and direction"))
end
Stream{format.T}(device, length(channels), SoapySDRDevice_setupStream(device, direction, string(format), map(x->x.idx, channels), length(channels), C_NULL))
Stream{format}(device, length(channels), SoapySDRDevice_setupStream(device, direction, soapy_format, map(x->x.idx, channels), length(channels), C_NULL))
end

function Stream(channels::Vector{Channel}; kwargs...)
native_format = promote_type(map(c -> native_stream_format(c)[1], channels)...) # native_stream_format -> (type, fullsclae)
if native_format <: AbstractComplexInteger
@warn "$(string(native_format)) may be poorly supported, it is recommend to specify a different type with Stream(format::Type, channels)"
end
Stream(native_format, channels; kwargs...)
end

function _read!(s::Stream{T}, buffers::NTuple{N, Vector{T}}; timeout=nothing) where {N, T}
timeout === nothing && (timeout = 0.1u"s") # Default from SoapySDR upstream
buflen = length(first(buffers))
@assert all(buffer->length(buffer) == buflen, buffers)
@assert N == s.nchannels
n, flags, timens = SoapySDRDevice_readStream(s.d, s, Ref(map(pointer, buffers)), buflen, uconvert(u"μs", timeout).val)
nread, flags, timens = SoapySDRDevice_readStream(s.d, s, Ref(map(pointer, buffers)), buflen, uconvert(u"μs", timeout).val)
timens = timens * u"ns"
n, flags, timens
nread, flags, timens
end

function Base.read!(s, buffers; kwargs...)
Expand All @@ -557,16 +549,31 @@ struct SampleBuffer{N, T}
end
Base.length(sb::SampleBuffer) = length(sb.bufs[1])

function Base.read(s::Stream{T}, n::Int; kwargs...) where {T}
"""
read(s::SoapySDR.Stream, nb::Integer; all=true)
Read at most nb bytes from s, returning a `SampleBuffer`
If all is true (the default), this function will block repeatedly trying to read all requested bytes, until an error or
end-of-file occurs. If all is false, at most one read call is performed, and the amount of data returned is device-dependent.
Note that not all stream types support the all option.
"""
function Base.read(s::Stream{T}, n::Int; all=true, kwargs...) where {T}
bufs = ntuple(_->Vector{T}(undef, n), s.nchannels)
nread, flags, timens = _read!(s, bufs; kwargs...)
if nread != n
# By definition of read, we can allow fewer samples than requested, unless all=false
if nread != n && all
@info "could not read requested length, suggest using read(...;all=false)"
@info("assertion debugging", nread, n)
@assert nread == n
end
SampleBuffer(bufs, flags, timens)
end

function Base.read(s::Stream; all=true, kwargs...)
read(s, s.mtu; all=true, kwargs...)
end

function activate!(s::Stream; flags = 0, timens = nothing, numElems=0)
SoapySDRDevice_activateStream(s.d, s, flags, timens === nothing ? 0 : uconvert(u"ns", timens).val, numElems)
nothing
Expand Down
14 changes: 14 additions & 0 deletions src/typemap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,17 @@ Type map from SoapySDR Stream formats to Julia types.
Note: Please see ComplexUInt and ComplexUInt if using 12 or 4 bit complex types.
"""
const _stream_type_jl2soapy = Dict{Type, String}(reverse.(_stream_type_pairs))

function _stream_map_jl2soapy(stream_type)
if !haskey(_stream_type_jl2soapy, stream_type)
error("Unsupported stream type: " + stream_type)
end
return _stream_type_jl2soapy[stream_type]
end

function _stream_map_soapy2jl(stream_type)
if !haskey(_stream_type_soapy2jl, stream_type)
error("Unsupported stream type: " + stream_type)
end
return _stream_type_soapy2jl[stream_type]
end
36 changes: 8 additions & 28 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,6 @@ end


@testset "SoapySDR.jl" begin
@testset "StreamFormat" begin

# Should not throw
sd.StreamFormat(sd.SOAPY_SDR_CF64)
sd.StreamFormat(sd.SOAPY_SDR_CF32)
sd.StreamFormat(sd.SOAPY_SDR_CS32)
sd.StreamFormat(sd.SOAPY_SDR_CU32)
sd.StreamFormat(sd.SOAPY_SDR_CS16)
sd.StreamFormat(sd.SOAPY_SDR_CU16)
sd.StreamFormat(sd.SOAPY_SDR_F64)
sd.StreamFormat(sd.SOAPY_SDR_F32)
sd.StreamFormat(sd.SOAPY_SDR_S32)
sd.StreamFormat(sd.SOAPY_SDR_U32)
sd.StreamFormat(sd.SOAPY_SDR_S16)
sd.StreamFormat(sd.SOAPY_SDR_U16)
sd.StreamFormat(sd.SOAPY_SDR_S8)
sd.StreamFormat(sd.SOAPY_SDR_U8)
sd.StreamFormat(sd.SOAPY_SDR_CS8)
sd.StreamFormat(sd.SOAPY_SDR_CU8)
sd.StreamFormat(sd.SOAPY_SDR_CS12)
sd.StreamFormat(sd.SOAPY_SDR_CU12)
sd.StreamFormat(sd.SOAPY_SDR_CS4)
sd.StreamFormat(sd.SOAPY_SDR_CU4)

# Should throw
@test_throws ErrorException sd.StreamFormat("nonsense")
end
@testset "Ranges/Display" begin
intervalrange = sd.SoapySDRRange(0, 1, 0)
steprange = sd.SoapySDRRange(0, 1, 0.1)
Expand Down Expand Up @@ -88,6 +61,8 @@ end
@test typeof(rx_chan) == sd.Channel
@test typeof(tx_chan) == sd.Channel

@test sd.native_stream_format(rx_chan)[1] == SoapySDR.ComplexInt{12} #, fullscale
@test sd.stream_formats(rx_chan) == [Complex{Int8}, SoapySDR.ComplexInt{12}, Complex{Int16}, ComplexF32]

# Test sensor API
sensor_list = sd.list_sensors(dev)
Expand Down Expand Up @@ -145,8 +120,13 @@ end


rx_stream = sd.Stream(ComplexF32, [rx_chan])

@test typeof(rx_stream) == sd.Stream{ComplexF32}
tx_stream = sd.Stream(ComplexF32, [tx_chan])
@test typeof(tx_stream) == sd.Stream{ComplexF32}

rx_stream = sd.Stream([rx_chan])
@test typeof(rx_stream) == sd.Stream{sd.ComplexInt{12}}
tx_stream = sd.Stream([tx_chan])
@test typeof(tx_stream) == sd.Stream{sd.ComplexInt{12}}
end
end

0 comments on commit 37a832e

Please sign in to comment.