. Stable asynchronous rtl-sdr data reading (threads, semaphore)
. ADSB results are shown in a ListIcon gadget
. Long frame signification is added in the ListIcon Comment column.
. RTL-SDR status is in Application StatusBar
Code: Select all
EnableExplicit
XIncludeFile "librtlsdr.pbi"
#SDR_SRATE = 2000000
#ADSB_FREQ = 1090000000
#SDR_GAIN = 372
#PREAMBLE_LEN = 16
#LONG_FRAME = 112
#SHORT_FRAME = 56
#MESSAGEGO = 253
#OVERWRITE = 254
#BAD_SAMPLE = 255
#BUF_LENGTH = 16*16384
#ALLOWED_ERRORS = 5
Enumeration FormWindow
#Window_Main
EndEnumeration
Enumeration FormGadget
#ListIcon_Frames
EndEnumeration
Structure ByteArray_Structure
a.a[0]
EndStructure
Structure ThreadParameter_Structure
Thread.i
Exit.i
EndStructure
Global *dev.rtlsdr_dev_t
Global *buf
Global ThreadParameter.ThreadParameter_Structure
Global hSem.i
Global Dim pythagore.a(128, 128)
Global Dim adsb_frame.a(13)
Global Dim buf.a(#BUF_LENGTH)
Global Dim thread_buf.a(#BUF_LENGTH)
Global font.i
Global lineCounter.i
Global lineMax.i = 300
Global framesCounter.i
Global ShowLongOnly.b = #False
Procedure pythagore_precompute()
Protected.i x, y
Protected.d scale
scale = 1.408
For x = 0 To 128
For y = 0 To 128
pythagore(x, y) = Round(scale * Sqr(x*x + y*y), #PB_Round_Nearest)
Next y
Next x
EndProcedure
Procedure.a abs8(x.a)
If x >= 128
ProcedureReturn x - 128
Else
ProcedureReturn 128 - x
EndIf
EndProcedure
Procedure.i magnitude(*buf.ByteArray_Structure, len.i)
Protected.i i
; takes IQ samples. Changes in place with the magnitude value (Char/uint8_t)
While i < len
*buf\a[i / 2] = pythagore(abs8(*buf\a[i]), abs8(*buf\a[i + 1]))
i + 2
Wend
;returns new buffer length
ProcedureReturn len / 2
EndProcedure
Procedure.i single_manchester(a.i, b.i, c.i, d.i)
Protected.i bit, bit_p
; takes 4 consecutive samples, return 0 or 1, #BADSAMPLE on error
If a > b
bit_p = 1
Else
bit_p = 0
EndIf
If c > d
bit = 1
Else
bit = 0
EndIf
; Sanity check two bits
If ((bit = 1) And (bit_p = 1) And (c > b) And (d < a))
ProcedureReturn 1
EndIf
If ((bit = 1) And (bit_p = 0) And (c > a) And (d < b))
ProcedureReturn 1
EndIf
If ((bit = 0) And (bit_p = 1) And (c < a) And (d > b))
ProcedureReturn 0
EndIf
If ((bit = 0) And (bit_p = 0) And (c < b) And (d > a))
ProcedureReturn 0
EndIf
ProcedureReturn #BAD_SAMPLE
EndProcedure
Procedure.i preamble(*buf.ByteArray_Structure, len.i, i.i)
Protected.i i2
Protected.a low, high
low.a = 0
high.a = 255
; Sequence Check. Returns 0/1 for preamble at index i
For i2 = 0 To #PREAMBLE_LEN - 1
Select i2
Case 0,2,7,9
high = *buf\a[i + i2]
Default
low = *buf\a[i + i2]
EndSelect
If high <= low
; No preamble sequence identified
ProcedureReturn 0
EndIf
Next i2
; Preamble sequence found
ProcedureReturn 1
EndProcedure
Procedure manchester(*buf.ByteArray_Structure, len.i)
Protected a.a, b.a, bit.a
Protected i.i, i2.i, errors.i
; Overwrites magnitude buffer with valid bits (#BADSAMPLE on errors)
While (i < len)
; Find preamble sequence
While i < len - #PREAMBLE_LEN
If preamble(*buf, len, i) = 0
i + 1
Continue
EndIf
a = *buf\a[i]
b = *buf\a[i + 1]
For i2 = 0 To #PREAMBLE_LEN - 1
*buf\a[i + i2] = #MESSAGEGO
Next i2
i = i + #PREAMBLE_LEN
Break
i + 1
Wend
i2 = i
errors = 0
; Mark bits until encoding breaks
While i < len
bit = single_manchester(a, b, *buf\a[i], *buf\a[i + 1])
a = *buf\a[i]
b = *buf\a[i + 1]
If bit = #BAD_SAMPLE
errors + 1
If errors > #ALLOWED_ERRORS
*buf\a[i2] = #BAD_SAMPLE
Break
Else
If a > b
bit = 1
Else
bit = 0
EndIf
a = 0
b = 255
EndIf
EndIf
*buf\a[i] = #OVERWRITE
*buf\a[i + 1] = #OVERWRITE
*buf\a[i2] = bit
i + 2
i2 + 1
Wend
Wend
EndProcedure
Procedure display(*frame.ByteArray_Structure, len.i)
Protected.i i, df, tc
Protected.s timeStamp, hexFrame, DFs, CA, AA, ICAO, PI, TCs, ST, Comment
Protected.s gadgetLine
; Downlink Format
df = (*frame\a[0] >> 3) & $1F
; All-call Reply (DF11) or Extended Squitter frames (DF17, DF18, DF19)
If Not ((df = 11) Or (df = 17) Or (df = 18) Or (df = 19))
ProcedureReturn
EndIf
timeStamp=FormatDate(" %yy/%mm/%dd %hh:%ii:%ss", Date())
For i = 0 To ((len + 7) / 8) - 1
hexFrame=hexFrame+RSet(Hex(adsb_frame(i),#PB_Ascii),2,"0")
Next i
DFs=Str(df)
; Mode S Transpondeur. CApability
CA=Str(*frame\a[0] & $07)
gadgetLine=timeStamp+Chr(10)+hexFrame+Chr(10)+DFs+Chr(10)+CA+Chr(10)
If df=11
; Short frame. All-call Reply
; Anounced Adress
AA=RSet(Hex(*frame\a[1] << 16 | (*frame\a[2] << 8) | *frame\a[3]), 6, "0")
; Parity/Interogator ID for short frame
PI=RSet(Hex(*frame\a[4] << 16 | (*frame\a[5] << 8) | *frame\a[5]), 6, "0")
gadgetLine=gadgetLine+AA+Chr(10)+PI
If ShowLongOnly= #False
AddGadgetItem(#ListIcon_Frames, -1, gadgetLine)
lineCounter+1
EndIf
Else
; Extended Squitter long frame (DF17, DF18, DF19)
; Aircraft Adress
ICAO=RSet(Hex(*frame\a[1] << 16 | (*frame\a[2] << 8) | *frame\a[3]), 6, "0")
; Parity/Interogator ID for long frame
PI=RSet(Hex(*frame\a[11] << 16 | (*frame\a[12] << 8) | *frame\a[13]), 6, "0")
; Type Code. Data frame content
tc=(*frame\a[4] >> 3) & $1F
TCs=Str(tc)
; SubType code
ST=Str(*frame\a[4] & $07)
; Comment - From Type Code value
Select tc
Case 1, 2, 3, 4
Comment="Aircraft identification"
Case 5, 6, 7, 8
Comment="Surface position"
Case 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
Comment="Airborne position - Barometric altitude"
Case 19
Comment="Airborne velocities"
Case 20, 21, 22
Comment="Airborne position - GNSS altitude"
Case 23, 24, 25, 26, 27
Comment="Reserved"
Case 28
Comment="Aircraft status"
Case 29
Comment="Target state and status"
Case 31
Comment="Aircraft operation status"
EndSelect
gadgetLine=gadgetLine+ICAO+Chr(10)+PI+Chr(10)+TCs+Chr(10)+ST+Chr(10)+Comment
AddGadgetItem(#ListIcon_Frames, -1, gadgetLine)
lineCounter+1
EndIf
framesCounter+1
StatusBarText(0, 2, " Frames Counter (Short + Long): "+Str(framesCounter+1))
; Windows API call. Automatic ListIcon gadget scrolling
SendMessage_(GadgetID(#ListIcon_Frames), #LVM_ENSUREVISIBLE, lineCounter-1, #True)
If lineCounter=lineMax
ClearGadgetItems(#ListIcon_Frames)
lineCounter=0
EndIf
EndProcedure
Procedure outmessages(*buf.ByteArray_Structure, len.i)
Protected.i i, data_i, index, shift, frame_len
Protected.a val_shift
While i < len
If *buf\a[i] > 1
i + 1
Continue
EndIf
frame_len = #LONG_FRAME
data_i = 0
For index = 0 To 13
adsb_frame(index) = 0
Next index
While (i < len) And (*buf\a[i] <= 1) And (data_i < frame_len)
If *buf\a[i] = 1
index = data_i / 8
shift = 7 - (data_i % 8)
adsb_frame(index) = adsb_frame(index) | 1 << shift
EndIf
If data_i = 7
If adsb_frame(0) = 0
Break
EndIf
If adsb_frame(0) & $80 <> 0
frame_len = #LONG_FRAME
Else
frame_len = #SHORT_FRAME
EndIf
EndIf
i + 1
data_i + 1
Wend
If data_i < frame_len - 1
i + 1
Continue
EndIf
display(@adsb_frame(0), frame_len)
i + 1
Wend
EndProcedure
Procedure initSDR()
Protected.i i, r, device_count
Protected.l dev_index
Protected.s notification
If UseLibrtlsdr()
; RTL-SDR System dll must be in application directory
device_count = rtlsdr_get_device_count()
If device_count = 0
notification="No SDR device found. Application shutdown"
StatusBarText(0, 3, notification)
MessageRequester("PlaneTracker", notification, #PB_MessageRequester_Warning)
Delay(3000)
End 1
EndIf
notification="Found " + Str(device_count) + " device(s)"
StatusBarText(0, 3, notification)
For i = 0 To device_count - 1
Debug("SDR Scan. "+Str(i) + ": " + PeekS(rtlsdr_get_device_name(i), -1, #PB_UTF8))
Next i
notification="SDR ID used " + Str(dev_index) + ": " + PeekS(rtlsdr_get_device_name(dev_index), -1, #PB_UTF8)
StatusBarText(0, 0, " "+notification)
r = rtlsdr_open(@*dev, dev_index)
If r < 0
notification="Failed to open SDR device #" + Str(dev_index)
StatusBarText(0, 3, notification)
Delay(3000)
End 1
EndIf
rtlsdr_set_center_freq(*dev, #ADSB_FREQ)
rtlsdr_set_sample_rate(*dev, #SDR_SRATE)
rtlsdr_set_tuner_gain(*dev, #SDR_GAIN)
rtlsdr_set_agc_mode(*dev, 0)
r = rtlsdr_reset_buffer(*dev)
If r < 0
notification="Failed to reset buffer"
StatusBarText(0, 3, notification)
Delay(3000)
EndIf
EndIf
EndProcedure
ProcedureC ReadBuffer(*buffer, size.l, *ctx)
CopyMemory(*buffer, @buf(0), size)
If IsThread(ThreadParameter\Thread)
SignalSemaphore(hSem)
EndIf
EndProcedure
Procedure ThreadProc(*Parameter.ThreadParameter_Structure)
Protected len.i
Repeat
WaitSemaphore(hSem)
If Not *Parameter\Exit
CopyMemory(@buf(0), @thread_buf(0), #BUF_LENGTH)
; ADSB data processing
len = magnitude(@thread_buf(0), #BUF_LENGTH)
manchester(@thread_buf(0), len)
outmessages(@thread_buf(0), len)
EndIf
Until *Parameter\Exit
EndProcedure
Procedure ReadAsyncThread(*Dummy)
Protected r.i
r = rtlsdr_read_async(*dev, @ReadBuffer(), 0, 32, #BUF_LENGTH)
EndProcedure
Define.i event, ReadThread
; Main code with thread/semaphore creation
OpenWindow(#Window_Main, 0, 0, 1024, 600, "ADSB SDR Async Reading", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
font=LoadFont(#PB_Any, "Consolas", 10)
CreateStatusBar(0, WindowID(#Window_Main))
AddStatusBarField(240)
StatusBarText(0, 0, "SDR Status")
AddStatusBarField(240)
StatusBarText(0, 1, "General information")
AddStatusBarField(240)
StatusBarText(0, 2, "Counter information")
AddStatusBarField(300)
StatusBarText(0, 3, "Status Detail")
ListIconGadget(#ListIcon_Frames, 2, 6, 1020, 566, "Time Stamp", 150)
AddGadgetColumn(#ListIcon_Frames, 1, "ADSB Frame", 220)
AddGadgetColumn(#ListIcon_Frames, 2, "DF", 30)
AddGadgetColumn(#ListIcon_Frames, 3, "CA", 30)
AddGadgetColumn(#ListIcon_Frames, 4, "AA-ICAO", 70)
AddGadgetColumn(#ListIcon_Frames, 5, "PI", 60)
AddGadgetColumn(#ListIcon_Frames, 6, "TC", 30)
AddGadgetColumn(#ListIcon_Frames, 7, "ST", 30)
AddGadgetColumn(#ListIcon_Frames, 8, "Comment", 300)
SetGadgetColor(#ListIcon_Frames, #PB_Gadget_FrontColor,RGB(0,64,128))
SetGadgetColor(#ListIcon_Frames, #PB_Gadget_BackColor,RGB(248,248,248))
SetGadgetFont(#ListIcon_Frames, FontID(font))
; SDR environment initialization
initSDR()
pythagore_precompute()
; Thread and semaphore creation
hSem = CreateSemaphore()
ThreadParameter\Thread = CreateThread(@ThreadProc(), @ThreadParameter)
ReadThread = CreateThread(@ReadAsyncThread(), #Null)
; Window loop event management
Repeat
Event = WaitWindowEvent()
Select Event
Case #PB_Event_Gadget
Select EventGadget()
; Gadget event
EndSelect
EndSelect
Until Event = #PB_Event_CloseWindow
; Cancel Async reading
rtlsdr_cancel_async(*dev)
If WaitThread(ReadThread, 3000) = 0
KillThread(ReadThread)
EndIf
; Cleaning ressources after shutdown
ThreadParameter\Exit = #True
If IsThread(ThreadParameter\Thread)
SignalSemaphore(hSem)
EndIf
If WaitThread(ThreadParameter\Thread, 1000) = 0
KillThread(ThreadParameter\Thread)
Debug ("ThreadProc killed")
EndIf
If hSem
FreeSemaphore(hSem)
EndIf
rtlsdr_close(*dev)
Debug ("Ressources cleaned and rtlsdr closed")
End