├── LR-FHSS ├── lrfh_cal_fft_squared.m ├── cal_Distance.m ├── lrfh_gen_hdr_bits.m ├── lrfh_init.m ├── lrfh_whitening_payload.m ├── lrfh_con_encode_hdr.m ├── lrfh_is_same_detected_pkt.m ├── lrfh_dewhitening_payload.m ├── lrfh_deinterleaving_hdr.m ├── lrfh_interleaving_hdr.m ├── lrfh_demodulate_header.m ├── lrfh_con_encode.m ├── lrfh_gen_data_frag_bits.m ├── lrfh_interleaving_payload.m ├── lrfh_deinterleaving_payload.m ├── lrfh_gen_ideal_waveform.m ├── lrfh_crc8.m ├── lrfh_lpfsig.m ├── lrfh_use_ideal_pkt_info.m ├── lrfh_crc16.m ├── lrfh_print_detected_pkt_info.m ├── lrfh_demod_smbls.m ├── lrfh_demodulate_payload.m ├── lrfh_gen_sig.m ├── lrfh_decode_hdr.m ├── lrfh_decode_payload.m ├── lrfh_fine_est_header.m ├── lrfh_print_decoded_pkt_info.m ├── lrfh_init_light.m ├── lrfh_sim.m ├── calculate_freq_from_hop_seq_id.m ├── lrfh_reconsig.m ├── lrfh_init_trellis.m ├── lrfh_get_overlapinfo.m ├── lrfh_detect_hdr.m ├── lrfh_decode_found_pkts.m ├── lrfh_detect_pkt.m └── peakfinder.m └── README.md /LR-FHSS/lrfh_cal_fft_squared.m: -------------------------------------------------------------------------------- 1 | function res = lrfh_cal_fft_squared(thissig) 2 | res = zeros(1,size(thissig,2)); 3 | for ant=1:size(thissig,1) 4 | DemodSig = thissig(ant,:); 5 | fftDemodSig = (fft(DemodSig)); 6 | res = res + abs(fftDemodSig).*abs(fftDemodSig); 7 | end 8 | -------------------------------------------------------------------------------- /LR-FHSS/cal_Distance.m: -------------------------------------------------------------------------------- 1 | function d = cal_Distance(x,y, usesoft) 2 | if usesoft 3 | x(x==0) = -1; 4 | tempp = x(1:length(y))-y; 5 | d = sum(tempp.*tempp); 6 | else 7 | y(y < 0) = 0; 8 | y(y > 0) = 1; 9 | d = sum(xor(x,y)); 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_gen_hdr_bits.m: -------------------------------------------------------------------------------- 1 | function headerbits = lrfh_gen_hdr_bits(state, decoded_header, LRF_cfg, knowinitstate_flag) 2 | header_enco = lrfh_con_encode_hdr(state,decoded_header,LRF_cfg.myTrellis_header,knowinitstate_flag); 3 | header_interl = lrfh_interleaving_hdr(header_enco); 4 | headerbits = [0 header_interl(1:40) LRF_cfg.SYNC_WORD header_interl(41:end)]; 5 | end 6 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_init.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | % NOTE: should run when start 6 | 7 | lrfh_USE_WRAPPER = 0; 8 | lrfh_init_light; 9 | load ../pkttrace/ALL_PKT_INFO_1.mat 10 | 11 | [y,LRF_cfg.iir] = lowpass(randn(1,100000), LRF_cfg.lowpassHz, LRF_cfg.samplingrate, ... 12 | 'ImpulseResponse','iir','StopbandAttenuation', 100, 'Steepness', 0.9); -------------------------------------------------------------------------------- /LR-FHSS/lrfh_whitening_payload.m: -------------------------------------------------------------------------------- 1 | function [data_out]=lrfh_whitening_payload(data_in,data_in_bytecount) 2 | lfsr = [1 1 1 1 1 1 1 1]; 3 | for index = 1:data_in_bytecount 4 | u = bitxor(data_in((index-1)*8+1:index*8), lfsr); 5 | data_out((index-1)*8+1:index*8-4)= u(5:8); 6 | data_out((index-1)*8+5:index*8) = u(1:4); 7 | lfsr = [lfsr(2:8) bitxor(lfsr(1), bitxor(lfsr(3), bitxor(lfsr(4),lfsr(5)) ) )]; 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_con_encode_hdr.m: -------------------------------------------------------------------------------- 1 | function [data_out, state]= lrfh_con_encode_hdr(state,data,myTrellis,knowinitstate_flag) 2 | if knowinitstate_flag 3 | runnum = 1; 4 | else 5 | runnum = 2; 6 | end 7 | for count=1:runnum 8 | data_out=[]; 9 | for idx=1:length(data) 10 | data_out=[data_out de2bi( myTrellis.outputs(state+1,data(idx)+1) ,2,'left-msb')]; 11 | state= mod( state*2 + data(idx) , 16); 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_is_same_detected_pkt.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function res = lrfh_is_same_detected_pkt(thispkt, thatpkt) 6 | res = 0; 7 | if abs(thispkt.start - thatpkt.start) < 1000 ... 8 | && thispkt.data_in_bitcount == thatpkt.data_in_bitcount ... 9 | && sum(abs(thispkt.hop_seq_id - thatpkt.hop_seq_id)) == 0 10 | if max(abs(thispkt.freq_Hz - thatpkt.freq_Hz)) < 30 11 | res = 1; 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_dewhitening_payload.m: -------------------------------------------------------------------------------- 1 | function [data_out]=lrfh_dewhitening_payload(data_in,data_in_bytecount) 2 | lfsr = [1 1 1 1 1 1 1 1]; 3 | for index = 1:data_in_bytecount 4 | data_out((index-1)*8+1:index*8-4)= data_in((index-1)*8+5:index*8); 5 | data_out((index-1)*8+5:index*8) = data_in((index-1)*8+1:index*8-4); 6 | data_out((index-1)*8+1:index*8) = bitxor(data_out((index-1)*8+1:index*8), lfsr); 7 | lfsr = [lfsr(2:8) bitxor(lfsr(1), bitxor(lfsr(3), bitxor(lfsr(4),lfsr(5)) ) )]; 8 | end 9 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_deinterleaving_hdr.m: -------------------------------------------------------------------------------- 1 | function [deint_hdr]= lrfh_deinterleaving_hdr(header) 2 | deinterleaver=[1, 23, 45, 63, 6, 28, 50, 68, 11, 33, 55, 73, 15, 37, 59, 77, 19, 41, 2, 24, 46, 64, 7, 29, 51, 69, 12, 34, 56, 74, 16, 38, 60, 78, 20, 42, 3, 25, 47,65, 8, 30, 52, 70, 13, 35, 57, 75, 17, 39, 61, 79, 21, 43, 4, 26, 48, 66, 9, 31, 53, 71, 14, 36, 58, 76, 18, 40, 62, 80, 22, 44, 5, 27, 49, 67, 10, 32, 54, 72 ]; 3 | 4 | temp=[header(2:41) header(74:end)]; 5 | 6 | deint_hdr=zeros(80,1)'; 7 | for idx=1:80 8 | deint_hdr(idx)=temp(deinterleaver(idx)); 9 | end 10 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_interleaving_hdr.m: -------------------------------------------------------------------------------- 1 | %-------------------------------------------------------------------------- 2 | function [int_hdr]= lrfh_interleaving_hdr(header) 3 | interleaver = [1 19 37 55 73 5 23 41 59 77 9 27 45 63 13 31 49 67 17 35 53 71 2 20 38 56 74 6 24 42 60 78 10 28 46 64 14 32 50 68 18 36 54 72 3 21 39 57 75 7 25 43 61 79 11 29 47 65 15 33 51 69 4 22 40 58 76 8 26 44 62 80 12 30 48 66 16 34 52 70]; 4 | int_hdr=zeros(80,1).'; 5 | for idx=1:80 6 | int_hdr(idx)=header(interleaver(idx)); 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_demodulate_header.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [bits, GMSKmodvaladj] = lrfh_demodulate_header(thispktsig,thisHeader,LRF_cfg) 6 | 7 | if thisHeader.shouldkeepflag 8 | local_osf = 1; 9 | local_offset = 0; 10 | local_smaple_time = thisHeader.smpltime; 11 | local_len = thisHeader.CoarseTime(2) - thisHeader.CoarseTime(1) + 1; 12 | thischecksig_0 = thispktsig(:,thisHeader.start-thisHeader.startofffset+local_offset:local_osf:thisHeader.start+local_len-thisHeader.startofffset-1+local_offset); 13 | thischecksig = lrfh_lpfsig(thischecksig_0,thisHeader.foundfreq,0,LRF_cfg); 14 | [bits, GMSKmodvaladj] = lrfh_demod_smbls(local_smaple_time,thischecksig,0,LRF_cfg); 15 | else 16 | bits = zeros(1,LRF_cfg.hdr_bit_num); 17 | GMSKmodvaladj = 0; 18 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_con_encode.m: -------------------------------------------------------------------------------- 1 | function [data_out,state]= lrfh_con_encode(state,data,myTrellis,CR) 2 | data_out=[]; 3 | for idx=1:length(data) 4 | data_out=[data_out de2bi( myTrellis.outputs(state+1,data(idx)+1) ,3,'left-msb')]; 5 | state= mod( state*2 + data(idx) , 64); 6 | end 7 | 8 | data_out_cr=[]; 9 | idx=1; 10 | if CR~=3 11 | matrix_ind=1; 12 | matrix=[1 1 0 0 1 0 1 0 0 0 1 0 1 0 0]; 13 | switch CR 14 | case 0 %5/6 15 | matrix_len=15; 16 | case 1 %2/3 17 | matrix_len=6; 18 | case 2 %1/2 19 | matrix_len=3; 20 | end 21 | for j=1:length(data_out) 22 | if matrix(matrix_ind) 23 | data_out_cr(idx)=data_out(j); 24 | idx=idx+1; 25 | end 26 | matrix_ind=matrix_ind+1; 27 | if matrix_ind == matrix_len+1 28 | matrix_ind=1; 29 | end 30 | end 31 | data_out=data_out_cr; 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_gen_data_frag_bits.m: -------------------------------------------------------------------------------- 1 | function fragment = lrfh_gen_data_frag_bits(thispkt, LRF_cfg) 2 | 3 | payload = thispkt.decoderes.dewhitening; 4 | CR = thispkt.CR; 5 | payload_len = thispkt.payload_length_de; 6 | payload_whiten = lrfh_whitening_payload(payload,payload_len); 7 | CRC = lrfh_crc16(payload_whiten); 8 | payload_CRC = [payload_whiten CRC 0 0 0 0 0 0]; 9 | payload_enco = lrfh_con_encode(0,payload_CRC,LRF_cfg.myTrellis,CR); 10 | payload_interl = lrfh_interleaving_payload(payload_enco); 11 | num_frags = ceil(length(payload_interl)/48); 12 | num_bits_frags = zeros(1,num_frags); 13 | num_bits_frags(1:end) = 50; 14 | num_bits_frags(end) = length(payload_interl) - 48*(num_frags-1)+2; 15 | leadpadding = [0]; 16 | for idx=1:num_frags 17 | if idx~=num_frags 18 | fragment{idx}.bits = [leadpadding payload_interl((idx-1)*48+1:idx*48) 0]; 19 | else 20 | fragment{num_frags}.bits = [leadpadding payload_interl((idx-1)*48+1:end) 0]; 21 | end 22 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_interleaving_payload.m: -------------------------------------------------------------------------------- 1 | function [data_out]= lrfh_interleaving_payload(data) 2 | data_in_bitcount=length(data); 3 | step = ceil( sqrt( data_in_bitcount ) ); 4 | step_v = floor(bitsra(step ,1)); 5 | step = bitshift(step ,1); 6 | st_idx = 1; 7 | st_idx_init = 1; 8 | pos=1; 9 | bits_left=data_in_bitcount; 10 | data_out=zeros(length(data),1); 11 | shift=0; 12 | while bits_left > 0 13 | in_row_width = bits_left; 14 | if in_row_width > 48 15 | in_row_width = 48; 16 | end 17 | for j =1:in_row_width 18 | data_out(j+shift)= data(pos); 19 | pos = pos+ step; 20 | if pos > data_in_bitcount 21 | st_idx = st_idx+ step_v; 22 | if st_idx > step 23 | st_idx_init=st_idx_init+1; 24 | st_idx = st_idx_init; 25 | end 26 | pos = st_idx; 27 | end 28 | end 29 | bits_left = bits_left- 48; 30 | shift=shift+48; 31 | end 32 | data_out=data_out.'; 33 | end 34 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_deinterleaving_payload.m: -------------------------------------------------------------------------------- 1 | function [deint_payload]= lrfh_deinterleaving_payload(payload,data_in_bitcount) 2 | %payload=[0 12 24 3 15 27 6 18 30 9 21 33 1 13 25 4 16 28 7 19 31 10 22 34 2 14 26 5 17 29 8 20 32 11 23 35]; 3 | %data_in_bitcount = 36; 4 | %payload=[0 14 28 42 3 17 31 45 6 20 34 48 9 23 37 51 12 26 40 1 15 29 43 4 18 32 46 7 21 35 49 10 24 38 52 13 27 41 2 16 30 44 5 19 33 47 8 22 36 50 11 25 39 53]; 5 | %data_in_bitcount = 54; 6 | %payload=[0 14 28 42 3 17 31 45 6 20 34 48 9 23 37 51 12 26 40 54 1 15 29 43 4 18 32 46 7 21 35 49 10 24 38 52 13 27 41 55 2 16 30 44 5 19 33 47 8 22 36 50 11 25 39 53 ]; 7 | %data_in_bitcount = 56; 8 | %step = floor( sqrt( data_in_bitcount ) ); 9 | y = 0; 10 | while y * y < data_in_bitcount 11 | y = y + 1; 12 | end 13 | step=y; 14 | step_v = floor(bitsra(step ,1)); 15 | step = bitshift(step ,1); 16 | st_idx = 0; 17 | st_idx_init = 0; 18 | pos=0; 19 | deint_payload=zeros(length(payload),1); 20 | %deint_payload(1)=payload(1); 21 | for i=2:length(payload) 22 | pos=pos+step; 23 | if pos >= data_in_bitcount 24 | st_idx = st_idx + step_v; 25 | if st_idx >= step 26 | st_idx_init=st_idx_init+1; 27 | st_idx = st_idx_init; 28 | end 29 | pos = st_idx; 30 | end 31 | deint_payload(pos)=payload(i); 32 | end 33 | deint_payload(2:end)=deint_payload(1:end-1); 34 | deint_payload(1)=payload(1); 35 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_gen_ideal_waveform.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function modsig0 = lrfh_gen_ideal_waveform(data,LRF_cfg) 6 | 7 | phasesmbl = [0:LRF_cfg.smblsmplnum-1]*LRF_cfg.phasechange/LRF_cfg.smblsmplnum; 8 | modsig0len = round(length(data)/LRF_cfg.BW*LRF_cfg.samplingrate); 9 | modsig0smblmid = round([0.5:1:length(data)]/LRF_cfg.BW*LRF_cfg.samplingrate); 10 | modsig0 = zeros(1,modsig0len); 11 | for h=1:length(data) 12 | if data(h)==0 13 | thissmbl = -phasesmbl; 14 | else 15 | thissmbl = phasesmbl; 16 | end 17 | thisbgn = round(modsig0smblmid(h)-LRF_cfg.smblsmplnum/2+1); 18 | thisend = thisbgn + LRF_cfg.smblsmplnum - 1; 19 | if h == 1 20 | initphase = 0; 21 | else 22 | initphase = modsig0(thisbgn-1); 23 | end 24 | modsig0(thisbgn:thisend) = thissmbl + initphase; 25 | end 26 | for h=3:length(data)-2 27 | if data(h-1) ~= data(h) 28 | if data(h-1) == 1 29 | thistransit = LRF_cfg.wave_1to0; 30 | else 31 | thistransit = -LRF_cfg.wave_1to0; 32 | end 33 | thiscenterloc = round(mean(modsig0smblmid(h-1:h))); 34 | thisstart = thiscenterloc - floor(length(thistransit)/2); 35 | modsig0(thisstart:thisstart+length(thistransit)-1) = modsig0(thisstart) + thistransit; 36 | end 37 | end 38 | tempp = find(modsig0 == 0); tempp(tempp==1) = []; 39 | modsig0(tempp) = modsig0(tempp-1); 40 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_crc8.m: -------------------------------------------------------------------------------- 1 | %-------------------------------------------------------------------------- 2 | function [crc]= lrfh_crc8(decoded) 3 | lr_fhss_header_crc8_lut = [ 0, 47, 94, 113, 188, 147, 226, 205, 87, 120, 9, 38, 235, 196, 181, 154, 174, 129, 240, 223, 18, 61, 76, 99, 249, 214, 167, 136, 69, 106, 27, 52, 115, 92, 45, 2, 207, 224, 145, 190, 36, 11, 122, 85, 152, 183, 198, 233, 221, 242, 131, 172, 97, 78, 63, 16, 138, 165, 212, 251, 54, 25, 104, 71, 230, 201, 184, 151, 90, 117, 4, 43, 177, 158, 239, 192, 13, 34, 83, 124, 72, 103, 22, 57, 244, 219, 170, 133, 31, 48, 65, 110, 163, 140, 253, 210, 149, 186, 203, 228, 41, 6, 119, 88, 194, 237, 156, 179, 126, 81, 32, 15, 59, 20, 101, 74, 135, 168, 217, 246, 108, 67, 50, 29, 208, 255, 142, 161, 227, 204, 189, 146, 95, 112, 1, 46, 180, 155, 234, 197, 8, 39, 86, 121, 77, 98, 19, 60, 241, 222, 175, 128, 26, 53, 68, 107, 166, 137, 248, 215, 144, 191, 206, 225, 44, 3, 114, 93, 199, 232, 153, 182, 123, 84, 37, 10, 62, 17, 96, 79, 130, 173, 220, 243, 105, 70, 55, 24, 213, 250, 139, 164, 5, 42, 91, 116, 185, 150, 231, 200, 82, 125, 12, 35, 238, 193, 176, 159, 171, 132, 245, 218, 23, 56, 73, 102, 252, 211, 162, 141, 64, 111, 30, 49, 118, 89, 40, 7, 202, 229, 148, 187, 33, 14, 127, 80, 157, 178, 195, 236, 216, 247, 134, 169, 100, 75, 58, 21, 143, 160, 209, 254, 51, 28, 109, 66 ]; 4 | crc = [1 1 1 1 1 1 1 1]; 5 | for k = 0:3 6 | pos =bi2de( bitxor( crc , decoded(k*8+1:k*8+8) ),'left-msb')+1; 7 | crc = de2bi(lr_fhss_header_crc8_lut(pos),8,'left-msb'); 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_lpfsig.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function outsig = lrfh_lpfsig(insig, freqinHz, filtoption, LRF_cfg) 6 | if filtoption == 0 7 | local_sampling_rate = LRF_cfg.samplingrate; 8 | else 9 | local_sampling_rate = LRF_cfg.fastsamplingrate; 10 | end 11 | thisnumcycles = freqinHz*(size(insig,2)/local_sampling_rate); 12 | thiswave = repmat(exp(1i*[0:size(insig,2)-1]*2*pi/size(insig,2)*thisnumcycles),size(insig,1),1); 13 | insig_dcvt = insig ./ thiswave; 14 | outsig = zeros(size(insig_dcvt)); 15 | filterchoice = 1; 16 | % tic 17 | for ant=1:size(insig,1) 18 | if filterchoice == 1 19 | if filtoption == 0 20 | outsig(ant,:) = filtfilt(LRF_cfg.iir,insig_dcvt(ant,:)); 21 | else 22 | outsig(ant,:) = filtfilt(LRF_cfg.fast_iir,insig_dcvt(ant,:)); 23 | end 24 | elseif filterchoice == 2 25 | outsig(ant,:) = lowpass(insig_dcvt(ant,:), LRF_cfg.lowpassHz, LRF_cfg.samplingrate, ... 26 | 'ImpulseResponse','iir','StopbandAttenuation', 100, 'Steepness', 0.9); 27 | elseif filterchoice == 4 28 | % inaccurate at low snr, faster 29 | herehalflen = ceil(length(LRF_cfg.fir)/2); 30 | tempp0 = insig_dcvt(ant,1:LRF_cfg.DSF:end); 31 | tempp1 = [tempp0, tempp0(1:herehalflen)]; 32 | tempp2 = filter(LRF_cfg.fir,1,tempp1); 33 | dsamplfsig = tempp2(herehalflen:herehalflen+length(tempp0)-1); 34 | tempplfexp = CPMA_interp(dsamplfsig, LRF_cfg.DSF); 35 | outsig(ant,1:length(tempplfexp)) = tempplfexp; 36 | end 37 | end 38 | % toc 39 | end 40 | 41 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_use_ideal_pkt_info.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function FoundDataSeg = lrfh_use_ideal_pkt_info(lrfh_sim_config, ALL_PKT_INFO, LRF_cfg) 6 | 7 | USE_FILE_IDX_array = lrfh_sim_config.USE_FILE_IDX_array; 8 | lrfh_sim_pkt_start = lrfh_sim_config.pkt_start; 9 | lrfh_sim_pkt_gird = lrfh_sim_config.pkt_gird; 10 | lrfh_sim_amp = lrfh_sim_config.amp; 11 | 12 | FoundDataSeg = cell(1,length(USE_FILE_IDX_array)); 13 | for pktidxidx=1:length(USE_FILE_IDX_array) 14 | pktidx = USE_FILE_IDX_array(pktidxidx); 15 | thispkt = ALL_PKT_INFO{pktidx}; 16 | 17 | offsethere = lrfh_sim_pkt_start(pktidxidx) - round(LRF_cfg.pkt_trace_offset/LRF_cfg.pkt_trace_downsampling_factor); 18 | thispkt.gridsel = lrfh_sim_pkt_gird(pktidxidx); 19 | thisgridfreq_Hz = (thispkt.gridsel - 1) * LRF_cfg.BW; 20 | thisaddcfo_Hz = lrfh_sim_config.addpktcfo_Hz(pktidxidx); 21 | ampmul = lrfh_sim_amp(pktidxidx); 22 | 23 | thispkt.start = round(ALL_PKT_INFO{pktidx}.start/LRF_cfg.pkt_trace_downsampling_factor) + offsethere; 24 | thispkt.SegCoarseTime = thispkt.SegCoarseTime + offsethere; 25 | thispkt.freq_Hz = thispkt.freq_Hz + thisgridfreq_Hz + thisaddcfo_Hz; 26 | thispkt.freq_header_Hz = thispkt.freq_header_Hz + thisgridfreq_Hz + thisaddcfo_Hz'; 27 | %thispkt.maxsyncval = thispkt.maxsyncval * ampmul^2; 28 | 29 | for hidx=1:length(thispkt.headerinfo) 30 | if ~isempty(thispkt.headerinfo{hidx}) 31 | thispkt.headerinfo{hidx}.start = round(thispkt.headerinfo{hidx}.start/LRF_cfg.pkt_trace_downsampling_factor) + offsethere; 32 | thispkt.headerinfo{hidx}.foundfreq = thispkt.headerinfo{hidx}.foundfreq + thisgridfreq_Hz + thisaddcfo_Hz; 33 | end 34 | end 35 | thispkt.CRCpass = 0; 36 | FoundDataSeg{pktidxidx} = thispkt; 37 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_crc16.m: -------------------------------------------------------------------------------- 1 | function [crc]= lrfh_crc16(decoded) 2 | lr_fhss_payload_crc16_lut = [0, 30043, 60086, 40941, 41015, 54636, 19073, 16346, 13621, 16494, 57219, 43736, 38146, 57433, 32692, 2799, 27242, 7985, 32988, 62855, 51805, 48902, 8427, 21936, 24415, 10756, 46569, 49330, 65384, 35379, 5598, 24709, 54484, 41359, 15970, 19257, 29923, 440, 40533, 60174, 57825, 38074, 2903, 32268, 16854, 13453, 43872, 56891, 48830, 52197, 21512, 8531, 7817, 27602, 62527, 33124, 35723, 65232, 24893, 5222, 11196, 24295, 49418, 46161, 56563, 43432, 13893, 17182, 31940, 2463, 38514, 58153, 59846, 40093, 880, 30251, 18929, 15530, 41799, 54812, 46745, 50114, 23599, 10612, 5806, 25589, 64536, 35139, 33708, 63223, 26906, 7233, 9115, 22208, 51501, 48246, 2087, 32124, 58001, 38858, 43024, 56651, 17062, 14333, 15634, 18505, 55204, 41727, 40229, 59518, 30611, 712, 25165, 5910, 35067, 64928, 49786, 46881, 10444, 23959, 22392, 8739, 48590, 51349, 63311, 33300, 7673, 26786, 52413, 47590, 9739, 21328, 27786, 6609, 34364, 62311, 63880, 36051, 4926, 26213, 22975, 11492, 45833, 50770, 42711, 54156, 19553, 14650, 1760, 29627, 60502, 39181, 37858, 59065, 31060, 3087, 13269, 18062, 55651, 44088, 6249, 27954, 62175, 34692, 47198, 52485, 21224, 10163, 11612, 22535, 51178, 45745, 36203, 63536, 26589, 4742, 29187, 1880, 39093, 60910, 53812, 42863, 14466, 19929, 18230, 12909, 44416, 55515, 59137, 37466, 3511, 30956, 4174, 25877, 64248, 36771, 45177, 50466, 23247, 12180, 9595, 20512, 53197, 47766, 34124, 61463, 28666, 6817, 31268, 3967, 37010, 58825, 55827, 44872, 12453, 17918, 20241, 14922, 42407, 53500, 61222, 39549, 1424, 28875, 50330, 45505, 11820, 23415, 25773, 4598, 36379, 64320, 61871, 34036, 6937, 28226, 20888, 9411, 47918, 52853, 44784, 56235, 17478, 12573, 3783, 31644, 58481, 37162, 39877, 61086, 29043, 1064, 15346, 20137, 53572, 42015 ]; 3 | 4 | crc = [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]; 5 | for k = 0:length(decoded)/8-1 6 | pos =bi2de( bitxor( crc(1:8) , decoded(k*8+1:k*8+8) ),'left-msb')+1; 7 | crc = bitxor( [crc(9:16) 0 0 0 0 0 0 0 0] ,de2bi(lr_fhss_payload_crc16_lut(pos),16,'left-msb')); 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Long-Range Frequency Hopping Spread Spectrum (LR-FHSS) Receiver Decoding Real-World LR-FHSS Packets (FOR EDUCATION AND ADADEMIC RESEARCH ONLY). 2 | 3 | Long Range-Frequency Hopping Spread Spectrum (LR-FHSS) is a new physical layer option that has been recently added to the LoRa family with the promise of achieving much higher network capacity than the previous versions of LoRa. 4 | 5 | LR-FHSS-receiver is capable of decoding LR-FHSS packets transmitted by sx126x or lr1110 devices. 6 | 7 | This webpage contains the source code of LR-FHSS-receiver written in Matlab. 8 | 9 | To test it, you may download the trace files collected in our experiments and feed the trace to LR-FHSS-receiver as input, or collect and use your own traces. 10 | Our trace files can be downloaded from “[pkttrace](https://zenodo.org/records/10965761)”. 11 | 12 | To run LR-FHSS-receiver, MATLAB is needed, along with the some toolboxes: such as Communications Toolbox, Signal Processing Toolbox and DSP System Toolbox. 13 | 14 | Our trace files were collected using sx126x device and from two different data rate which are DR 8 and DR 9. For each DR, 100 traces have been uploaded, name of traces range between 1 to 500 for DR8 and between 501 to 1000 for DR9. Each trace file contains 1 packet where the payload length size range between 8-16 bytes. 15 | 16 | To run LR-FHSS-receiver, after downloading the source file, there should a directory, named LRFHSS, which is the source code directory. The main file, named lrfh_sim.m, can be found under the LRFHSS directory. The trace data should be downloaded to another directory, which can be called pkttrace and can be at the same level as LRFHSS. You may then simply open Matlab, go to the LRFHSS directory, and type “lrfh_sim” in the command window. 17 | 18 | To test different traces, you may open lrfh_sim.m and modify two variables. One is to select the trace, such as: lrfh_sim_config.USE_FILE_IDX = 1; and the other is to set the corresponding Data Rate, such as: lrfh_sim_config.drsel = LRF_cfg.CONST_use_DR8; After the program finishes, the content of packets is printed, such as: [ca, c4, 24, 6a, 92, 36, d4, 58, 20, 08, 10, b4, 36, b2, 8e, 02] 19 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_print_detected_pkt_info.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [] = lrfh_print_detected_pkt_info(idealFoundDataSeg,FoundDataSeg,lrfh_sim_config) 6 | 7 | pkt_detected_flag = zeros(1,length(idealFoundDataSeg)); 8 | for pktidx=1:length(FoundDataSeg) 9 | thispkt = FoundDataSeg{pktidx}; 10 | maptoidxidx = 0; 11 | for ipktidx=1:length(idealFoundDataSeg) 12 | thatpkt = idealFoundDataSeg{ipktidx}; 13 | if lrfh_is_same_detected_pkt(thispkt, thatpkt) 14 | pkt_detected_flag(ipktidx) = pktidx; 15 | maptoidxidx = ipktidx; 16 | break; 17 | end 18 | end 19 | if maptoidxidx > 0 20 | fprintf(1,'found pkt %d (actual %d, trace %d): ', pktidx, maptoidxidx, lrfh_sim_config.USE_FILE_IDX_array(maptoidxidx)); 21 | else 22 | fprintf(1,'found pkt %d (do not know which one): ', pktidx); 23 | end 24 | fprintf(1,'start %d, numfrags %d, payload %d (b), CR %d, with header %d, power %.2f, headers [', ... 25 | thispkt.start, thispkt.num_frags, thispkt.payload_length_bits, thispkt.CR, thispkt.foundwithhdr, thispkt.estpower); 26 | for h=1:thispkt.header_count 27 | if ~isempty(FoundDataSeg{pktidx}.headerinfo{h}) 28 | fprintf(1,'Y'); 29 | else 30 | fprintf(1,'N'); 31 | end 32 | if h < thispkt.header_count 33 | fprintf(1,', '); 34 | else 35 | fprintf(1,'], '); 36 | end 37 | end 38 | fprintf(1, 'freq_Hz [') 39 | for fidx=1:thispkt.num_frags 40 | fprintf(1,'%.2f', thispkt.freq_Hz(fidx)); 41 | if fidx < thispkt.num_frags 42 | fprintf(1,', '); 43 | else 44 | fprintf(1,']\n'); 45 | end 46 | end 47 | end 48 | for ipktidx=1:length(idealFoundDataSeg) 49 | if pkt_detected_flag(ipktidx) == 0 50 | thispkt = idealFoundDataSeg{ipktidx}; 51 | fprintf(1,'(actual %d, trace %d): start %d, numfrags %d, payload %d (b), CR %d -- NOT FOUND!\n', ... 52 | ipktidx, lrfh_sim_config.USE_FILE_IDX_array(ipktidx), thispkt.start, thispkt.num_frags, thispkt.payload_length_bits, thispkt.CR); 53 | end 54 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_demod_smbls.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [GMSKbitvals, GMSKmodvaladj_Hz] = lrfh_demod_smbls(smpltime,thischecksig, demodoption, LRF_cfg) 6 | 7 | headerphase = angle(thischecksig); 8 | GMSKbitvals = zeros(1,length(smpltime)); 9 | if demodoption == 0 10 | locallookdist = LRF_cfg.lookdist; 11 | localphaseslope = LRF_cfg.phaseslope; 12 | else 13 | locallookdist = LRF_cfg.fastlookdist; 14 | localphaseslope = LRF_cfg.fastphaseslope; 15 | end 16 | for bidx=1:length(smpltime) 17 | thissmplloc = smpltime(bidx); 18 | 19 | heredist = round(locallookdist*2/(LRF_cfg.demodusenum-1)); 20 | usesamps = thischecksig(:,thissmplloc-locallookdist:heredist:thissmplloc+locallookdist); 21 | tempp = sum(transpose(usesamps.*conj(usesamps))); 22 | W = tempp/sum(tempp); 23 | 24 | alldiff = headerphase(:,thissmplloc+locallookdist) - headerphase(:,thissmplloc-locallookdist); 25 | tempp = find(abs(alldiff) > pi/2); 26 | alldiff(tempp) = alldiff(tempp) - sign(alldiff(tempp))*2*pi; 27 | alldiff(find(abs(alldiff)> pi/2)) = 0; 28 | allk = alldiff/(locallookdist*2); 29 | 30 | k = sum(allk.*W'); 31 | GMSKbitvals(bidx) = k/localphaseslope; 32 | end 33 | 34 | tempp0 = prctile(abs(GMSKbitvals),80); 35 | useidx = find(abs(GMSKbitvals)= 0); 42 | end 43 | lookidx = intersect(lookidx0,useidx); 44 | thisval = mean(GMSKbitvals(lookidx)); 45 | if zzz == 1 46 | adjmeasure(zzz,:) = [length(lookidx)/length(useidx), thisval+1]; 47 | else 48 | adjmeasure(zzz,:) = [length(lookidx)/length(useidx), thisval-1]; 49 | end 50 | end 51 | GMSKmodvaladj = sum(adjmeasure(:,1).*adjmeasure(:,2)); 52 | 53 | if LRF_cfg.adapttofreqdrift_flag 54 | GMSKbitvals = GMSKbitvals - GMSKmodvaladj; 55 | end 56 | % slope adj, slope amount of phase in one symbol time. 57 | GMSKmodvaladj_Hz = GMSKmodvaladj*localphaseslope/2/pi/LRF_cfg.baudrate; 58 | 59 | tempp = find(abs(GMSKbitvals)>LRF_cfg.demod_soft_val_cap); 60 | GMSKbitvals(tempp) = sign(GMSKbitvals(tempp))*LRF_cfg.demod_soft_val_cap; -------------------------------------------------------------------------------- /LR-FHSS/lrfh_demodulate_payload.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [thisdataseg, GMSKmodvaladj] = lrfh_demodulate_payload(thispktsig,thisDataSegTime,thisDataSegFreqHz,LRF_cfg,maxbitnum, overlapinfo, estpower, CR_de) 6 | local_ant_num = size(thispktsig,1); 7 | thischecksig_0 = thispktsig(:,thisDataSegTime(1):thisDataSegTime(2)); 8 | thischecksig = lrfh_lpfsig(thischecksig_0,thisDataSegFreqHz,0,LRF_cfg); 9 | thisdataseg.start = LRF_cfg.lookdist+1; 10 | tempp = round(([0:maxbitnum-1]/LRF_cfg.BW)*LRF_cfg.samplingrate); 11 | thisdataseg.smpltime = thisdataseg.start + tempp; 12 | 13 | [GMSKbitvals, GMSKmodvaladj] = lrfh_demod_smbls(thisdataseg.smpltime,thischecksig,0,LRF_cfg); 14 | 15 | if LRF_cfg.data_demod_use_interf_erasure_flag == 1 16 | tempp = sum(overlapinfo.interf_flag_mat'); % NOTE: any freq has some overlap is a read flag 17 | tempp1 = find(tempp); 18 | tempp1(tempp1 > length(GMSKbitvals)) = []; 19 | GMSKbitvals(tempp1) = 0; 20 | elseif LRF_cfg.data_demod_use_interf_erasure_flag == 5 21 | % NOTE: packet power estimated with SYNC_WORD coherently. So, have 22 | % to divide by the length to remove the coherency effect. Have to 23 | % to divide by the length again to get to the power per sample 24 | hereadjratio = ones(1, length(GMSKbitvals)); 25 | estpower_per_smpl = estpower/length(LRF_cfg.SYNC_vec)/length(LRF_cfg.SYNC_vec)/LRF_cfg.antnum; 26 | estnoisep = ones(1,length(GMSKbitvals))*LRF_cfg.sim_est_noise_P_per_smpl_after_LPF; 27 | estsigp = estpower_per_smpl*ones(1,length(GMSKbitvals)); 28 | estinterfp = overlapinfo.invSIR(1:length(GMSKbitvals))*estpower_per_smpl; 29 | estSNR = estsigp./estnoisep; 30 | estSINR = estsigp./(estnoisep + estinterfp); 31 | if CR_de == 3 32 | thisthresh = LRF_cfg.SINRcap_val(1); 33 | else 34 | thisthresh = LRF_cfg.SINRcap_val(2); 35 | end 36 | needtochangeidx = find(estSINR < thisthresh); 37 | if length(needtochangeidx) 38 | estSNR(needtochangeidx) = min(thisthresh, estSNR(needtochangeidx)); 39 | hereadjratio(needtochangeidx) = estSINR(needtochangeidx)./estSNR(needtochangeidx); 40 | end 41 | GMSKbitvals = GMSKbitvals.*hereadjratio; 42 | end 43 | thisdataseg.bits = GMSKbitvals; -------------------------------------------------------------------------------- /LR-FHSS/lrfh_gen_sig.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [LRFHSS_time_sig_clean,LRFHSS_sim_noise_sig, LRFHSS_sim_sig_tx_count, LRFHSS_time_sig_sep_clean] = lrfh_gen_sig(lrfh_sim_config,LRF_cfg) 6 | 7 | if strcmp(lrfh_sim_config.sim_channel, 'AWGN') == 0 8 | LRFHSS_time_sig_clean = zeros(LRF_cfg.antnum,lrfh_sim_config.siglen); 9 | else 10 | LRFHSS_time_sig_clean = zeros(1,lrfh_sim_config.siglen); 11 | end 12 | LRFHSS_sim_sig_tx_count = zeros(1,lrfh_sim_config.siglen); 13 | LRFHSS_time_sig_sep_clean = []; 14 | local_save_sig_flag = (length(lrfh_sim_config.USE_FILE_IDX_array) <=2) && LRF_cfg.check_SIC_flag; 15 | 16 | local_print_flag = 1; 17 | if local_print_flag 18 | fprintf(1, 'lrfh sim genterating signal: ') 19 | for h=1:20 fprintf(1, ' '); end 20 | end 21 | for useidx=1:length(lrfh_sim_config.USE_FILE_IDX_array) 22 | USE_FILE_IDX = lrfh_sim_config.USE_FILE_IDX_array(useidx); 23 | 24 | if local_print_flag 25 | for h=1:20 fprintf(1, '\b'); end 26 | fprintf(1, 'pkt %4d, trace %4d', useidx, USE_FILE_IDX); 27 | end 28 | thisfilename = sprintf('../pkttrace/%d', USE_FILE_IDX); 29 | thisfile = fopen(thisfilename); A = fread(thisfile, 'int16'); fclose(thisfile); 30 | B = A(1:2:end) + 1i*A(2:2:end); A=[]; B=transpose(B); 31 | 32 | % NOTE: have to keep this selection code because pkt info was obtained with this 33 | maxampval = max(abs(B)); 34 | thresh = maxampval/3; % NOTE: the SNR has to be 20 dB or more to be safe! 35 | tempp = find(abs(B) > thresh); 36 | useB = B(max(1,tempp(1)-LRF_cfg.tracemargin):min(tempp(end)+LRF_cfg.tracemargin,length(B))); 37 | 38 | useB = useB/max(abs(useB)); 39 | useB = useB(1:LRF_cfg.pkt_trace_downsampling_factor:end); 40 | 41 | thisgrid = lrfh_sim_config.pkt_gird(useidx); 42 | thisgridfreq_Hz = (thisgrid - 1) * LRF_cfg.BW; 43 | thisaddcfoHz = lrfh_sim_config.addpktcfo_Hz(useidx); 44 | thisfreqshiftwave = exp(1i*[0:length(useB)-1]*2*pi/length(useB)*(thisgridfreq_Hz+thisaddcfoHz)*(length(useB)/LRF_cfg.samplingrate)); 45 | outp = useB.*thisfreqshiftwave; 46 | 47 | if strcmp(lrfh_sim_config.sim_channel, 'ETU') 48 | cpchcfg = LRF_cfg.chcfg; cpchcfg.Seed = lrfh_sim_config.pkt_channseed(useidx); 49 | outp = transpose(lteFadingChannel(cpchcfg, transpose(outp))); 50 | end 51 | channel_sig_P = sum(sum(outp.*conj(outp))); 52 | outp = outp/sqrt(channel_sig_P/numel(outp)); 53 | 54 | thisbgn = lrfh_sim_config.pkt_start(useidx); 55 | thisend = thisbgn + length(outp) - 1; 56 | thissig = outp*lrfh_sim_config.amp(useidx); 57 | if local_save_sig_flag 58 | LRFHSS_time_sig_sep_clean{useidx} = zeros(size(LRFHSS_time_sig_clean)); 59 | LRFHSS_time_sig_sep_clean{useidx}(:,thisbgn:thisend) = thissig; 60 | end 61 | LRFHSS_time_sig_clean(:,thisbgn:thisend) = LRFHSS_time_sig_clean(:,thisbgn:thisend) + thissig; 62 | LRFHSS_sim_sig_tx_count(thisbgn:thisend) = LRFHSS_sim_sig_tx_count(thisbgn:thisend) + 1; 63 | end 64 | LRFHSS_sim_noise_sig = (randn(size(LRFHSS_time_sig_clean)) + 1i*randn(size(LRFHSS_time_sig_clean)))/sqrt(2); 65 | if local_print_flag 66 | fprintf(1, '\n'); 67 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_decode_hdr.m: -------------------------------------------------------------------------------- 1 | function [decoded_hdr, decoded_hdr_info]=lrfh_decode_hdr(deint_header, myTrellis) 2 | 3 | decoded_hdr = []; 4 | decoded_hdr_info.CRCpass = 0; 5 | 6 | decodescores = zeros(myTrellis.numStates,2); 7 | alltryres = cell(1,myTrellis.numStates); 8 | 9 | outbitsvals = [ 10 | 0 0; 11 | 0 1; 12 | 1 0; 13 | 1 1 14 | ]; 15 | precalcostvals = zeros(length(deint_header)/2,size(outbitsvals,1)); 16 | for i=1:length(deint_header)/2 17 | thisrcv = deint_header(i*2-1:i*2); 18 | for val=0:size(outbitsvals,1)-1 19 | precalcostvals(i,val+1) = cal_Distance(outbitsvals(val+1,:), thisrcv, 1); 20 | end 21 | end 22 | 23 | for idx=1:myTrellis.numStates 24 | [this_decoded_hdr,match,min_cost] = vit_decode(idx-1,precalcostvals,myTrellis); 25 | decodescores(idx,:) = [match,min_cost]; 26 | alltryres{idx} = this_decoded_hdr; 27 | if match 28 | [~, encode_state]= lrfh_con_encode_hdr(0,this_decoded_hdr,myTrellis,1); 29 | break; 30 | end 31 | end 32 | allpassidx = find(decodescores(:,1)==1); allfailidx = find(decodescores(:,1)==0); 33 | if length(allpassidx) 34 | decodescores(allfailidx,2) = 10000; 35 | [min_cost_val,min_cost_idx] = min(decodescores(:,2)); 36 | decoded_hdr = alltryres{min_cost_idx}; 37 | decoded_hdr_info.CRCpass = 1; 38 | decoded_hdr_info.payloadlen = bi2de(decoded_hdr(1:8),'left-msb'); 39 | decoded_hdr_info.modulation = decoded_hdr(9:11); 40 | decoded_hdr_info.CR = bi2de(decoded_hdr(12:13),'left-msb'); 41 | decoded_hdr_info.grid = decoded_hdr(14); 42 | decoded_hdr_info.hop = decoded_hdr(15); 43 | decoded_hdr_info.BW = decoded_hdr(16:19); 44 | decoded_hdr_info.hopseq = decoded_hdr(20:28); 45 | decoded_hdr_info.syncindex = sum(decoded_hdr(29:30).*[2,1]); 46 | decoded_hdr_info.syncindexbit = decoded_hdr(29:30); 47 | decoded_hdr_info.futureuse = decoded_hdr(31:32); 48 | decoded_hdr_info.CRC = decoded_hdr(33:end); 49 | decoded_hdr_info.state = encode_state+1;%min_cost_idx; 50 | decoded_hdr_info.min_cost = min_cost_val; 51 | else 52 | [min_cost_val,min_cost_idx] = min(decodescores(:,2)); 53 | decoded_hdr = alltryres{min_cost_idx}; 54 | decoded_hdr_info.CRCpass = 0; 55 | decoded_hdr_info.state = min_cost_idx; 56 | decoded_hdr_info.min_cost = min_cost_val; 57 | end 58 | end 59 | 60 | function [data_out,match,min_cost]= vit_decode(state,precalcostvals,myTrellis) 61 | len = size(precalcostvals,1) ; 62 | cost_string=ones(16,3)*1000; cost_string(state+1,2:3) = 0; 63 | decoded_string = zeros(16,len); 64 | new_decoded_string = zeros(16,len); 65 | for i=1:len 66 | costvals = precalcostvals(i,:); 67 | for h=1:8 68 | for hh=1:2 69 | t1 = cost_string(h,2) + costvals(myTrellis.outputs(h,hh)+1); 70 | t2 = cost_string(h+8,2) + costvals(myTrellis.outputs(h+8,hh)+1); 71 | if t1 tbdepth 9 | decoded_pay = vitdec(deint_payload_quant,LRF_cfg.myTrellis,tbdepth,'term','soft',8); 10 | else 11 | decoded_pay = zeros(1,floor(length(deint_payload)/3)); decoded_pay(end-24:end) = 1; 12 | end 13 | elseif CR_de == 1 14 | puncpat = [1 1 0 0 1 0]; 15 | decoded_pay = vitdec(deint_payload_quant,LRF_cfg.myTrellis,tbdepth,'term','soft',8,puncpat); 16 | end 17 | 18 | crc=lrfh_crc16(decoded_pay(1:end-16-6).'); 19 | match=isequal(crc.',decoded_pay(end-15-6:end-6)); 20 | decoded_pay = decoded_pay(1:end-16-6); 21 | min_cost = 0; 22 | else 23 | [decoded_pay,match,min_cost] = vit_decode_payload(0,deint_payload,LRF_cfg.myTrellis,CR_de); 24 | end 25 | end 26 | 27 | 28 | function [data_out,match,min_cost]= vit_decode_payload(state,vals,myTrellis,CR) 29 | vitusesoft_flag = 1; 30 | vals = vals.'; 31 | 32 | if CR==3 33 | k=3; 34 | elseif CR==2 35 | k=2; 36 | elseif CR==1 37 | k=3/2; 38 | elseif CR==0 39 | k=6/5; 40 | end 41 | 42 | cost_string = zeros(64,3); cost_string(2:end,2) = 10000; 43 | decoded_string = zeros(64,floor(length(vals)/k )); 44 | new_decoded_string = zeros(64,floor(length(vals)/k)); 45 | 46 | for i=1:floor(length(vals)/k) 47 | 48 | if CR==3 49 | r=vals(i*3-2:i*3); sh=0; len=3; 50 | elseif CR==2 51 | r=vals(i*2-1:i*2); sh=1; len=2; 52 | elseif CR==1 53 | if mod(i,2)==1 %odd 54 | r=vals((i-1)/2*3+1:(i-1)/2*3+2); sh=1; len=2; 55 | else %even 56 | r=vals((i-2)/2*3+3); sh=1; len=1; 57 | end 58 | elseif CR==0 59 | if mod(i,5)==0 60 | r=vals(i/5*6); sh=2; len=1; 61 | elseif mod(i,5)==1 62 | r=vals((i-1)/5*6+1:(i-1)/5*6+2); sh=1; len=2; 63 | elseif mod(i,5)==2 64 | r=vals((i-2)/5*6+3); 65 | elseif mod(i,5)==3 66 | r=vals((i-3)/5*6+4); sh=2; len=1; 67 | elseif mod(i,5)==4 68 | r=vals((i-4)/5*6+5); 69 | end 70 | end 71 | 72 | for h=1:32 73 | for hh=1:2 74 | if (CR==1 && mod(i,2)==0) || (CR==0 && mod(i,5)==2) || (CR==0 && mod(i,5)==4) 75 | num=de2bi(myTrellis.outputs(h,hh),3,'left-msb'); 76 | num=num(2); 77 | t1=cost_string(h,2)+cal_Distance( num , r, vitusesoft_flag ); 78 | num=de2bi(myTrellis.outputs(h+32,hh),3,'left-msb'); 79 | num=num(2); 80 | t2=cost_string(h+32,2)+cal_Distance( num , r, vitusesoft_flag); 81 | else 82 | t1=cost_string(h,2)+cal_Distance(de2bi(floor(bitsra(myTrellis.outputs(h,hh),sh)),len,'left-msb'),r, vitusesoft_flag); 83 | t2=cost_string(h+32,2)+cal_Distance(de2bi(floor(bitsra(myTrellis.outputs(h+32,hh),sh)),len,'left-msb'),r, vitusesoft_flag); 84 | end 85 | if t1 max(scanvals0(:,topfidx))*0.25 42 | thisHeader.shouldkeepflag = 0; 43 | else 44 | scanvals = zeros(length(Toff_array),length(cfo_array)); 45 | 46 | tlookout = 4; 47 | trange =[max(1,toptidx-tlookout):min(toptidx+tlookout,length(Toff_array))]; 48 | topfidx_adj = topfidx - 1; 49 | if topfidx_adj > length(LRF_cfg.SYNC_WORD)/2 50 | topfidx_adj = topfidx_adj - length(LRF_cfg.SYNC_WORD); 51 | end 52 | topfidx_adj = -topfidx_adj; 53 | flookout = 1; 54 | herefmin = max((topfidx_adj-flookout)*2*pi/length(LRF_cfg.SYNC_WORD),cfo_array(1)); 55 | herefmax = min((topfidx_adj+flookout)*2*pi/length(LRF_cfg.SYNC_WORD),cfo_array(end)); 56 | tempp1 = abs(cfo_array-herefmin); [t,a1] = min(tempp1); 57 | tempp2 = abs(cfo_array-herefmax); [t,a2] = min(tempp2); 58 | frange =[a1:a2]; 59 | for tidx=1:length(trange) 60 | thisbgn = Toff_array(trange(tidx)); 61 | for ant=1:local_ant_num 62 | thissamples = thischecksig(ant,thisbgn+syncsampleoffset); 63 | for cidx=1:length(frange) 64 | thiscfo = cfo_array(frange(cidx)); 65 | thisadjwave = exp(1i*[0:length(LRF_cfg.SYNC_WORD)-1]*thiscfo); 66 | tempp = thissamples.*thisadjwave./LRF_cfg.SYNC_vec; 67 | scanvals(trange(tidx),frange(cidx)) = scanvals(trange(tidx),frange(cidx)) + abs(sum(tempp))*abs(sum(tempp)); 68 | end 69 | end 70 | end 71 | 72 | maxsyncval = max(max(scanvals)); 73 | [optidx,opcidx] = find(scanvals==maxsyncval); 74 | op_sig_bgn = Toff_array(optidx) - round(LRF_cfg.sync_start_bit/LRF_cfg.BW*LRF_cfg.samplingrate) + thisHdrCoarseTime(1); 75 | op_cfo = cfo_array(opcidx)/LRF_cfg.smblsmplnum; 76 | thisHeader.start = max(op_sig_bgn,thisHeader.startofffset+1); % NOTE: this is the start of the segment 77 | thiscfo_Hz = -(op_cfo*LRF_cfg.finesiglen/(2*pi))/(LRF_cfg.finesiglen/LRF_cfg.samplingrate); 78 | thisHeader.cfo = thiscfo_Hz; % NOTE: this is the CFO after the coarse correction 79 | thisHeader.foundfreq = thisHdrCoarseFreqHz + thiscfo_Hz; 80 | thisHeader.maxsyncval = maxsyncval; 81 | 82 | tempp = round(([0:LRF_cfg.hdr_bit_num-1]/LRF_cfg.BW)*LRF_cfg.samplingrate); 83 | thisHeader.smpltime = thisHeader.startofffset + tempp; 84 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_print_decoded_pkt_info.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | %{ 6 | sta_decoded_flag: 7 | 8 | 1. detected in round 9 | 2. detected as packet 10 | 3. decoded in round 11 | 4. decoded as packet 12 | 5. decode cost 13 | 6. found header number 14 | 7. packet trace index 15 | %} 16 | 17 | function [sta_decoded_flag, drinfo] = lrfh_print_decoded_pkt_info(idealFoundDataSeg, lrfh_sim_result, lrfh_sim_config) 18 | 19 | drinfo = zeros(2); 20 | if lrfh_sim_config.use_ursp_flag == 0 21 | 22 | sta_decoded_flag = zeros(length(lrfh_sim_config.USE_FILE_IDX_array),7); 23 | for ipktidx=1:length( lrfh_sim_config.USE_FILE_IDX_array) 24 | traceidx = lrfh_sim_config.USE_FILE_IDX_array(ipktidx); 25 | sta_decoded_flag(ipktidx,7) = traceidx; 26 | thispkt = idealFoundDataSeg{ipktidx}; 27 | for try_count=1:length(lrfh_sim_result) 28 | FoundDataSeg = lrfh_sim_result{try_count}; 29 | for pktidx=1:length(FoundDataSeg) 30 | thatpkt = FoundDataSeg{pktidx}; 31 | if lrfh_is_same_detected_pkt(thispkt, thatpkt) && sta_decoded_flag(ipktidx,1) == 0 32 | sta_decoded_flag(ipktidx,1) = try_count; 33 | sta_decoded_flag(ipktidx,2) = pktidx; 34 | tempp = 0; 35 | for uuu=1:thatpkt.header_count 36 | if ~isempty(thatpkt.headerinfo{uuu}) 37 | tempp = tempp + 1; 38 | end 39 | end 40 | sta_decoded_flag(ipktidx,6) = tempp; 41 | break; 42 | end 43 | end 44 | for pktidx=1:length(FoundDataSeg) 45 | thatpkt = FoundDataSeg{pktidx}; 46 | if thatpkt.CRCpass && lrfh_is_same_detected_pkt(thispkt, thatpkt) 47 | if length(thispkt.decoderes.dewhitening) == length(thatpkt.decoderes.dewhitening) 48 | if sum(abs(thispkt.decoderes.dewhitening - thatpkt.decoderes.dewhitening)) == 0 ... 49 | && sta_decoded_flag(ipktidx,3) == 0 50 | sta_decoded_flag(ipktidx,3) = try_count; 51 | sta_decoded_flag(ipktidx,4) = pktidx; 52 | sta_decoded_flag(ipktidx,5) = thatpkt.decoderes.cost; 53 | maptoidxidx = ipktidx; 54 | break; 55 | end 56 | end 57 | end 58 | end 59 | end 60 | fprintf(1,'pkt %d -- CR %d, traceidx %d -- ', ipktidx, thispkt.CR, traceidx); 61 | if sta_decoded_flag(ipktidx, 1) == 0 62 | fprintf(1,'NOT DETECTED -- '); 63 | else 64 | fprintf(1,'detected in attempt %d as pkt %d -- ', ... 65 | sta_decoded_flag(ipktidx,1), sta_decoded_flag(ipktidx,2)); 66 | end 67 | if sta_decoded_flag(ipktidx, 3) == 0 68 | fprintf(1,'NOT DECODED\n'); 69 | else 70 | fprintf(1,'decoded in attempt %d as pkt %d cost %.2f\n', ... 71 | sta_decoded_flag(ipktidx,3), sta_decoded_flag(ipktidx,4), sta_decoded_flag(ipktidx,5)); 72 | end 73 | end 74 | dr8idx = find(sta_decoded_flag(:,7) <= 500); 75 | dr9idx = find(sta_decoded_flag(:,7) > 500); 76 | dr8gotnum = length(find(sta_decoded_flag(dr8idx,3))); 77 | dr9gotnum = length(find(sta_decoded_flag(dr9idx,3))); 78 | 79 | if length(dr8idx) 80 | fprintf(1, 'DR8 PRR %.2f (%d / %d)\n', dr8gotnum/length(dr8idx), dr8gotnum, length(dr8idx)); 81 | end 82 | if length(dr9idx) 83 | fprintf(1, 'DR9 PRR %.2f (%d / %d)\n', dr9gotnum/length(dr9idx), dr9gotnum, length(dr9idx)); 84 | end 85 | drinfo = [length(dr8idx), dr8gotnum; length(dr9idx), dr9gotnum]; 86 | else 87 | sta_decoded_flag = []; 88 | FoundDataSeg = lrfh_sim_result{1}; 89 | FoundDataSeg_2 = lrfh_sim_result{2}; 90 | for pktidx=1:length(FoundDataSeg_2) 91 | FoundDataSeg{length(FoundDataSeg)+1} = FoundDataSeg_2{pktidx}; 92 | end 93 | tokeeplag = ones(1,length(FoundDataSeg)); 94 | for pktidx=1:length(FoundDataSeg) 95 | thispkt = FoundDataSeg{pktidx}; 96 | if thispkt.CRCpass == 0 97 | tokeeplag(pktidx) = 0; 98 | end 99 | end 100 | for pktidx=1:length(FoundDataSeg)-1 101 | thispkt = FoundDataSeg{pktidx}; 102 | if tokeeplag(pktidx) == 0 103 | continue; 104 | end 105 | for pktidx2=pktidx+1:length(FoundDataSeg) 106 | thatpkt = FoundDataSeg{pktidx2}; 107 | if tokeeplag(pktidx2) == 0 108 | continue; 109 | end 110 | if thispkt.CR == thatpkt.CR && thispkt.payload_length_bits == thatpkt.payload_length_bits 111 | tempp = thispkt.decoderes.foundpayload - thatpkt.decoderes.foundpayload; 112 | if sum(abs(tempp))==0 113 | tokeeplag(pktidx2) = 0; 114 | end 115 | end 116 | end 117 | end 118 | fprintf(1, '\n\nProcessing USRP packets. Decoded %d unique packets.\n\n', sum(tokeeplag)); 119 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_init_light.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | LRF_cfg.region_val_EU = 0; 6 | LRF_cfg.region_val_US = 1; 7 | LRF_cfg.region = LRF_cfg.region_val_EU; 8 | 9 | if LRF_cfg.region == LRF_cfg.region_val_EU 10 | LRF_cfg.pkt_trace_sampling_rate = 500000; % 11 | LRF_cfg.pkt_trace_downsampling_factor = 3; % 12 | LRF_cfg.allBW = 137000; 13 | elseif LRF_cfg.region == LRF_cfg.region_val_US 14 | LRF_cfg.pkt_trace_sampling_rate = 2000000; % 15 | LRF_cfg.pkt_trace_downsampling_factor = 1; % 16 | LRF_cfg.allBW = 1523000; 17 | end 18 | 19 | LRF_cfg.decode_try_num = 3; 20 | LRF_cfg.viterbi_use_soft_flag = 1; 21 | LRF_cfg.data_demod_use_interf_erasure_flag = 5; % NOTE: 0: no CAED; 1: old CAED; 5: new CAED 22 | 23 | LRF_cfg.use_new_SIC_flag = 1; 24 | 25 | LRF_cfg.SYNC_WORD = '00101100000011110111100110010101'-'0'; 26 | % NOTE: starts at bit 42 in the header 27 | LRF_cfg.SYNC_phasevals = [0]; 28 | for h=2:length(LRF_cfg.SYNC_WORD) 29 | if LRF_cfg.SYNC_WORD(h) ~= LRF_cfg.SYNC_WORD(h-1) 30 | LRF_cfg.SYNC_phasevals(h) = LRF_cfg.SYNC_phasevals(h-1); 31 | else 32 | if LRF_cfg.SYNC_WORD(h) == 1 33 | LRF_cfg.SYNC_phasevals(h) = LRF_cfg.SYNC_phasevals(h-1) + pi/2; 34 | else 35 | LRF_cfg.SYNC_phasevals(h) = LRF_cfg.SYNC_phasevals(h-1) - pi/2; 36 | end 37 | end 38 | end 39 | LRF_cfg.SYNC_vec = exp(1i*LRF_cfg.SYNC_phasevals); 40 | 41 | LRF_cfg.pkt_trace_offset = 50000; % 42 | 43 | LRF_cfg.samplingrate = round(LRF_cfg.pkt_trace_sampling_rate/LRF_cfg.pkt_trace_downsampling_factor); % 44 | LRF_cfg.grid_num = 8; 45 | LRF_cfg.BW = 488; 46 | LRF_cfg.baudrate = 1/LRF_cfg.BW; 47 | LRF_cfg.smblsmplnum = round(LRF_cfg.samplingrate*LRF_cfg.baudrate); 48 | LRF_cfg.staytime_data = 0.1024; % sec 49 | LRF_cfg.staytime_hdr = 0.233472; % sec 50 | LRF_cfg.staysmplnum_data = round(LRF_cfg.staytime_data*LRF_cfg.samplingrate); 51 | LRF_cfg.staysmplnum_hdr = round(LRF_cfg.staytime_hdr*LRF_cfg.samplingrate); 52 | LRF_cfg.hdr_num = 2; 53 | LRF_cfg.sync_start_bit = 41; 54 | LRF_cfg.hdr_bit_num = 113; 55 | LRF_cfg.lowpassHz = 200; 50*2000000/LRF_cfg.samplingrate; 56 | LRF_cfg.max_freq_drift_Hz = 12000; 57 | 58 | LRF_cfg.demod_soft_val_cap = 1; 59 | 60 | LRF_cfg.sync_scan_seconds = 0.05; % NOTE: has been 0.05 61 | LRF_cfg.sync_scan_step = round(LRF_cfg.samplingrate*LRF_cfg.sync_scan_seconds); 62 | LRF_cfg.synccheckpeakrangehalf = ceil((LRF_cfg.allBW+2*LRF_cfg.max_freq_drift_Hz)/LRF_cfg.samplingrate*LRF_cfg.sync_scan_step/2); 63 | 64 | LRF_cfg.lookdist = round(LRF_cfg.smblsmplnum/4); 65 | LRF_cfg.demodusenum = 4; 66 | 67 | LRF_cfg.DSF = 10; 68 | 69 | LRF_cfg.recon_seg_len_symbol_num = 10; 70 | 71 | LRF_cfg.tracemargin = round(200*LRF_cfg.samplingrate/2000000); 72 | LRF_cfg.antnum = 2; 73 | LRF_cfg.maxhdrnum = 3; 74 | LRF_cfg.phasechange = pi/2; 75 | LRF_cfg.phaseslope = LRF_cfg.phasechange/LRF_cfg.smblsmplnum; 76 | 77 | LRF_cfg.use_grid_freq_Hz = 100; 78 | LRF_cfg.use_grid_time_sec = 1/LRF_cfg.BW; 79 | LRF_cfg.use_grid_time_sampnum = round(LRF_cfg.samplingrate*LRF_cfg.use_grid_time_sec); 80 | LRF_cfg.use_grid_freq_num = ceil(LRF_cfg.allBW*1.65/LRF_cfg.use_grid_freq_Hz); % NOTE: used to be 1.25, to deal with CFO, changed to 1.65 to deal with NTN 81 | tempp = ceil(LRF_cfg.BW/LRF_cfg.use_grid_freq_Hz); 82 | LRF_cfg.use_grid_freq_idx_offset = [0:tempp-1] - floor(tempp/2); 83 | LRF_cfg.coherelensec = 0.001; % channel cohereence len 84 | LRF_cfg.coheresmpnum = round(LRF_cfg.coherelensec*LRF_cfg.samplingrate); 85 | LRF_cfg.max_pkt_len_sec = 1.8; 86 | LRF_cfg.finesiglen = ceil(LRF_cfg.smblsmplnum*LRF_cfg.hdr_bit_num*1.02); 87 | 88 | lrfh_init_trellis; 89 | 90 | LRF_cfg.interf_alarm_thresh = 1.5; 91 | LRF_cfg.adapttofreqdrift_flag = 1; 92 | LRF_cfg.max_SIC_time_off_smpl_num = round(0.1/LRF_cfg.BW*LRF_cfg.samplingrate); 93 | 94 | LRF_cfg.chcfg.DelayProfile = 'ETU'; 95 | LRF_cfg.chcfg.NRxAnts = LRF_cfg.antnum; 96 | LRF_cfg.chcfg.DopplerFreq = 5; % orig: 5 97 | LRF_cfg.chcfg.MIMOCorrelation = 'Low'; 98 | LRF_cfg.chcfg.Seed = 1; 99 | LRF_cfg.chcfg.InitPhase = 'Random'; 100 | LRF_cfg.chcfg.ModelType = 'GMEDS'; 101 | LRF_cfg.chcfg.NTerms = 16; 102 | LRF_cfg.chcfg.NormalizeTxAnts = 'On'; 103 | LRF_cfg.chcfg.NormalizePathGains = 'On'; 104 | LRF_cfg.chcfg.SamplingRate = LRF_cfg.samplingrate; 105 | LRF_cfg.chcfg.InitTime = 0; 106 | 107 | LRF_cfg.InterfP = [0.0062 0.0071 0.0084 0.0108 0.0146 0.0206 0.0294 0.0419 0.0589 0.0811 0.1092 0.1436 0.1844 0.2311 0.2834 0.3402 0.4004 0.4626 0.5252 0.5871 0.6465 0.7022 0.7530 0.7981 0.8373 0.8700 0.8963 0.9166 0.9311 0.9402 0.9441 0.9428 0.9365 0.9248 0.9074 0.8842 0.8548 0.8190 0.7770 0.7292 0.6764 0.6195 0.5595 0.4978 0.4356 0.3749 0.3168 0.2626 0.2134 0.1699 0.1322 0.1007 0.0751 0.0549 0.0395 0.0282 0.0202 0.0149 0.0114 0.0092 0.0079]; 108 | LRF_cfg.InterfF = [-600:20:600]; 109 | LRF_cfg.InterfP = LRF_cfg.InterfP/max(LRF_cfg.InterfP); 110 | LRF_cfg.sim_est_noise_P_per_smpl_after_LPF = 0.0014; 111 | LRF_cfg.SINRcap_val = [5,10]; % DR8, DR9 112 | 113 | p(1) = -1.7223e-05; 114 | p(2) = 0.0042; 115 | p(3) = -0.0042; 116 | hereidx = [1:241]; 117 | f = polyval(p,hereidx); 118 | LRF_cfg.wave_1to0 = f; 119 | 120 | LRF_cfg.SIREraThresh = 2; % NOTE: outdated 121 | LRF_cfg.CAEDlowerboundSNR = 10; % NOTE: outdated 122 | 123 | LRF_cfg.DR89traceidx = [1 500; 501 1000]; 124 | LRF_cfg.max_payload_byte_num = 100; 125 | 126 | LRF_cfg.CONST_use_DR8 = 1; 127 | LRF_cfg.CONST_use_DR9 = 2; 128 | LRF_cfg.CONST_use_bothDR = 3; 129 | 130 | LRF_cfg.dbg_plot_phase_flag = 0; 131 | LRF_cfg.for_link_test_flag = 0; 132 | LRF_cfg.check_SIC_flag = 1; -------------------------------------------------------------------------------- /LR-FHSS/lrfh_sim.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | lrfh_init; 6 | 7 | lrfh_sim_config.gen_trace_flag = 1; 8 | lrfh_sim_config.use_ursp_flag = 0; 9 | 10 | if lrfh_sim_config.use_ursp_flag == 1 11 | lrfh_sim_config.gen_trace_flag = 0; 12 | end 13 | lrfh_sim_config.decode_try_num = LRF_cfg.decode_try_num; 14 | 15 | if lrfh_sim_config.gen_trace_flag 16 | 17 | % --------- sim config main bgn --------------------------------------- 18 | if lrfh_USE_WRAPPER == 0 19 | lrfh_sim_config.drsel = LRF_cfg.CONST_use_DR8; % NOTE: 1: DR8, 2, DR9, 3, bothDR 20 | lrfh_sim_config.pkt_num = 2; 21 | lrfh_sim_config.base_SNR_dB = -17; 22 | lrfh_sim_config.PDiff_dB = 20; 23 | lrfh_sim_config.sim_channel = 'ETU'; % 'AWGN' 'ETU' 24 | lrfh_sim_config.siglensec = 10; 25 | lrfh_sim_config.run_detection_flag = 1; % NOTE: 0: ideal; 1 run it, 26 | end 27 | % --------- sim config main end --------------------------------------- 28 | 29 | if lrfh_sim_config.drsel == LRF_cfg.CONST_use_DR8 30 | herenum = [lrfh_sim_config.pkt_num 0]; 31 | elseif lrfh_sim_config.drsel == LRF_cfg.CONST_use_DR9 32 | herenum = [0 lrfh_sim_config.pkt_num]; 33 | else 34 | tempp = round(lrfh_sim_config.pkt_num/2); 35 | herenum = [tempp lrfh_sim_config.pkt_num-tempp]; 36 | end 37 | lrfh_sim_config.USE_FILE_IDX_array = []; 38 | for dridx=1:2 39 | thisnum = herenum(dridx); 40 | hererange = diff(LRF_cfg.DR89traceidx(dridx,:))+1; 41 | addedidx = []; 42 | if thisnum > 0 43 | while length(addedidx) < thisnum 44 | tempp = randperm(hererange); 45 | herepicknum = min(thisnum-length(addedidx),hererange); 46 | tempp1 = tempp(1:herepicknum) + LRF_cfg.DR89traceidx(dridx,1) - 1; 47 | addedidx = [addedidx, tempp1]; 48 | end 49 | end 50 | lrfh_sim_config.USE_FILE_IDX_array = [lrfh_sim_config.USE_FILE_IDX_array addedidx]; 51 | end 52 | 53 | lrfh_sim_config.siglen = lrfh_sim_config.siglensec * LRF_cfg.samplingrate; 54 | lrfh_sim_config.SNR_dB = rand(1,lrfh_sim_config.pkt_num)*lrfh_sim_config.PDiff_dB + lrfh_sim_config.base_SNR_dB; 55 | if lrfh_sim_config.drsel == LRF_cfg.CONST_use_bothDR 56 | tempp = find(lrfh_sim_config.USE_FILE_IDX_array > 500); 57 | lrfh_sim_config.SNR_dB(tempp) = lrfh_sim_config.SNR_dB(tempp) + 4; % DR9 with more dB 58 | end 59 | lrfh_sim_config.pkt_start = ceil(sort(rand(1,lrfh_sim_config.pkt_num))*(lrfh_sim_config.siglen-LRF_cfg.max_pkt_len_sec*LRF_cfg.samplingrate)); % NOTE: assume pkt is no longer than 0.2 of the sim time 60 | lrfh_sim_config.pkt_gird = ceil(rand(1,lrfh_sim_config.pkt_num)*LRF_cfg.grid_num); 61 | lrfh_sim_config.addpktcfo_Hz = zeros(1,lrfh_sim_config.pkt_num); 62 | 63 | tempp = lrfh_sim_config.SNR_dB + 10*log10((LRF_cfg.allBW / LRF_cfg.samplingrate)); 64 | lrfh_sim_config.amp = sqrt(power(10, tempp/10)); 65 | lrfh_sim_config.pkt_channseed = ceil(rand(1,lrfh_sim_config.pkt_num)*1000000); 66 | lrfh_sim_config.pkt_ntn_delayspread = 10e-9 + 30e-9*rand(1,lrfh_sim_config.pkt_num); 67 | 68 | [LRFHSS_time_sig_clean,LRFHSS_sim_noise_sig,LRFHSS_sim_sig_tx_count,LRFHSS_time_sig_sep_clean] = lrfh_gen_sig(lrfh_sim_config,LRF_cfg); 69 | end 70 | if lrfh_sim_config.use_ursp_flag 71 | thisfilename = 'your file name'; 72 | fprintf(1, 'reading %s\n', thisfilename); 73 | thisfile = fopen(thisfilename,'r'); 74 | a = fread(thisfile, 'int16'); % or 'float' 75 | fclose(thisfile); 76 | LRFHSS_time_sig_clean = transpose(a(1:2:end) + 1i*a(2:2:end)); 77 | clear a; 78 | LRFHSS_sim_noise_sig = zeros(size(LRFHSS_time_sig_clean)); 79 | 80 | lrfh_sim_config.run_detection_flag = 1; % NOTE: 0: ideal; 1 run it, 81 | lrfh_sim_config.drsel = LRF_cfg.CONST_use_DR9; % NOTE: 1: DR8, 2, DR9, 3, bothDR 82 | lrfh_sim_config.siglen = size(LRFHSS_time_sig_clean,2); 83 | end 84 | LRFHSS_time_sig = LRFHSS_time_sig_clean + LRFHSS_sim_noise_sig; 85 | 86 | if lrfh_sim_config.use_ursp_flag == 0 87 | if lrfh_sim_config.pkt_num == 1 88 | lrfh_sim_config.decode_try_num = 1; 89 | end 90 | end 91 | if lrfh_sim_config.use_ursp_flag == 0 92 | FoundDataSeg = lrfh_use_ideal_pkt_info(lrfh_sim_config, ALL_PKT_INFO, LRF_cfg); 93 | idealFoundDataSeg = FoundDataSeg; 94 | else 95 | idealFoundDataSeg = []; 96 | end 97 | lrfh_sim_result = cell(1,LRF_cfg.decode_try_num); 98 | for lrfh_sim_decode_try_count=1:lrfh_sim_config.decode_try_num 99 | if lrfh_sim_config.run_detection_flag == 1 100 | [HeaderOutput,FoundDataSeg] = lrfh_detect_pkt(lrfh_sim_decode_try_count,LRFHSS_time_sig,lrfh_sim_result,LRF_cfg); 101 | lrfh_print_detected_pkt_info(idealFoundDataSeg,FoundDataSeg,lrfh_sim_config); 102 | else 103 | todolist = []; 104 | for pktidx=1:length(FoundDataSeg) 105 | if FoundDataSeg{pktidx}.CRCpass == 0 106 | todolist{length(todolist)+1} = FoundDataSeg{pktidx}; 107 | end 108 | end 109 | FoundDataSeg = todolist; 110 | end 111 | [FoundSigGird, FoundDataSeg] = lrfh_get_overlapinfo(FoundDataSeg,floor(size(LRFHSS_time_sig,2)/LRF_cfg.use_grid_time_sampnum),LRF_cfg); 112 | [FoundDataSeg, LRFHSS_recon_time_sig] = lrfh_decode_found_pkts(FoundDataSeg,LRFHSS_time_sig,LRF_cfg); 113 | LRFHSS_time_sig = LRFHSS_time_sig - LRFHSS_recon_time_sig; 114 | lrfh_sim_result{lrfh_sim_decode_try_count} = FoundDataSeg; 115 | end 116 | [sta_decoded_flag, drinfo] = lrfh_print_decoded_pkt_info(idealFoundDataSeg, lrfh_sim_result, lrfh_sim_config); -------------------------------------------------------------------------------- /LR-FHSS/calculate_freq_from_hop_seq_id.m: -------------------------------------------------------------------------------- 1 | function [freq] = calculate_freq_from_hop_seq_id(grid, enable_hop, header_count, BW, hop_seq_id, num_frag) 2 | current_hop = 0; 3 | [status,n_grid,lfsr_state,polynomial,xoring_seed,hop_seq_id] = lr_fhss_get_hop_params( grid, BW, hop_seq_id ); 4 | if status == 0 %LR_FHSS_STATUS_OK 5 | % Skip the hop frequencies inside the set [0, 4 - header_count): 6 | if enable_hop ~= 0 7 | for i = 0:4-header_count-1 8 | [hop, lfsr_state]=lr_fhss_get_next_state(lfsr_state, n_grid,polynomial,xoring_seed ); 9 | 10 | end 11 | end 12 | freq=[]; 13 | for idx=1:num_frag+header_count 14 | [next_freq_in_pll_steps,lfsr_state] = sx126x_lr_fhss_get_next_freq_in_pll_steps( lfsr_state, grid, n_grid, enable_hop, polynomial, xoring_seed, hop_seq_id,current_hop, header_count ) ; 15 | %next_freq_in_pll_steps = sx126x_lr_fhss_get_next_freq_in_pll_steps( lfsr_state, grid, n_grid, enable_hop, polynomial, xoring_seed, hop_seq_id,hop, header_count ) 16 | %[hop, lfsr_state]=lr_fhss_get_next_state(lfsr_state, n_grid,polynomial,xoring_seed ); 17 | freq = [ freq next_freq_in_pll_steps ]; 18 | current_hop=current_hop+1; 19 | end 20 | % freq=freq(header_count+1:end); 21 | 22 | end 23 | end 24 | %------------------------------------------ 25 | function [status,n_grid,initial_state,polynomial,xoring_seed,hop_sequence_id_de]=lr_fhss_get_hop_params( grid, bw, hop_sequence_id ) 26 | lr_fhss_channel_count =[ 80, 176, 280, 376, 688, 792, 1480, 1584, 3120, 3224 ]; 27 | lr_fhss_lfsr_poly1 = [ 33, 45, 48, 51, 54, 57 ]; 28 | lr_fhss_lfsr_poly2 = [ 65, 68, 71, 72 ]; 29 | lr_fhss_lfsr_poly3 = [ 142, 149 ]; 30 | 31 | channel_count = lr_fhss_channel_count(bi2de( bw,'left-msb')+1); 32 | 33 | if grid == 1 34 | n_grid = channel_count / 8; 35 | else 36 | n_grid = channel_count / 52; 37 | end 38 | 39 | hop_sequence_id_de=bi2de( hop_sequence_id,'left-msb'); 40 | xoring_seed=zeros(1,16); 41 | switch n_grid 42 | case {10, 22, 28, 30, 35, 47} 43 | initial_state = 6; 44 | polynomial = lr_fhss_lfsr_poly1( bi2de( hop_sequence_id(1:3),'left-msb') +1 ); 45 | xoring_seed(end-5:end) = hop_sequence_id(4:9); 46 | status= 0;%LR_FHSS_STATUS_OK; 47 | if hop_sequence_id_de >= 384 48 | status= 3;%LR_FHSS_STATUS_ERROR; 49 | end 50 | case {60, 62} 51 | initial_state = 56; 52 | polynomial = lr_fhss_lfsr_poly1( bi2de( hop_sequence_id(1:3),'left-msb') +1 ); 53 | xoring_seed(end-5:end) = hop_sequence_id(4:9); 54 | status= 0;%LR_FHSS_STATUS_OK; 55 | if hop_sequence_id_de >= 384 56 | status= 3;%LR_FHSS_STATUS_ERROR; 57 | end 58 | case {86, 99} 59 | initial_state = 6; 60 | polynomial = lr_fhss_lfsr_poly2( bi2de( hop_sequence_id(1:2),'left-msb') +1 ); 61 | xoring_seed(end-6:end) = hop_sequence_id(3:9); 62 | status= 0;%LR_FHSS_STATUS_OK; 63 | case {185, 198} 64 | initial_state = 6; 65 | polynomial = lr_fhss_lfsr_poly3( bi2de( hop_sequence_id(1),'left-msb') +1 ); 66 | xoring_seed(end-7:end) = hop_sequence_id(2:9); 67 | status= 0;%LR_FHSS_STATUS_OK; 68 | case {390, 403} 69 | initial_state = 6; 70 | polynomial = 264; 71 | xoring_seed(end-8:end) = hop_sequence_id; 72 | status= 0;%LR_FHSS_STATUS_OK; 73 | otherwise 74 | status= 3;%LR_FHSS_STATUS_ERROR; 75 | end 76 | 77 | initial_state=de2bi(initial_state,16,'left-msb'); 78 | polynomial=de2bi(polynomial,16,'left-msb'); 79 | end 80 | %------------------------------------------ 81 | function [hop, lfsr_state]= lr_fhss_get_next_state(lfsr_state, n_grid, polynomial, xoring_seed) 82 | while 1 83 | lsb = bitand(lfsr_state(end), 1); 84 | lfsr_state(2:end)=lfsr_state(1:end-1); 85 | lfsr_state(1)=0; 86 | if lsb 87 | lfsr_state =bitxor(lfsr_state,polynomial); 88 | end 89 | hop = xoring_seed; 90 | if ~isequal(hop, lfsr_state) 91 | hop =bitxor(hop, lfsr_state); 92 | end 93 | if bi2de( hop,'left-msb') <= n_grid 94 | break; 95 | end 96 | end 97 | 98 | hop= bi2de( hop,'left-msb') - 1; 99 | end 100 | %------------------------------------------ 101 | function [freq, lfsr_state]= sx126x_lr_fhss_get_next_freq_in_pll_steps(lfsr_state, grid, n_grid, enable_hop, polynomial, xoring_seed, hop_sequence_id,current_hop, header_count ) 102 | 103 | %execute if HOP_AT_CENTER_FREQ is defined 104 | if 0%HOP_AT_CENTER_FREQ 105 | freq_table = 0; 106 | grid_offset = 0; 107 | else 108 | [freq_table,lfsr_state] =lr_fhss_get_next_freq_in_grid(enable_hop, lfsr_state, n_grid, polynomial, xoring_seed, hop_sequence_id ); 109 | 110 | if grid ==1 111 | nb_channel_in_grid = 8; 112 | else 113 | nb_channel_in_grid = 52; 114 | end 115 | grid_offset = (1 + mod(n_grid, 2 )) * ( nb_channel_in_grid / 2 ); 116 | end 117 | 118 | center_freq_in_pll_steps= 910163968; %for trace 1-6 and > 12 119 | %center_freq_in_pll_steps= 959447040; %for trace 7-12 120 | grid_in_pll_steps = sx126x_lr_fhss_get_grid_in_pll_steps( grid ); 121 | freq = - freq_table * grid_in_pll_steps -( 0 + grid_offset ) * 512; 122 | 123 | 124 | %execute if HOP_AT_CENTER_FREQ is not defined 125 | if 1%~HOP_AT_CENTER_FREQ 126 | % Perform frequency correction for every other sync header 127 | if enable_hop && ( current_hop < header_count ) 128 | if mod( ( header_count - current_hop ) , 2 ) == 0 129 | % OFFSET_SYNCWORD = 488.28125 / 2, and FREQ_STEP_SX1261_2 = 0.95367431640625, so 130 | % OFFSET_SYNCWORD / FREQ_STEP_SX1261_2 = 256 131 | freq = freq + 256; 132 | end 133 | end 134 | end 135 | 136 | end 137 | 138 | %------------------------------------------ 139 | function [PLL_STEPS] = sx126x_lr_fhss_get_grid_in_pll_steps( grid ) 140 | if grid ==1 141 | PLL_STEPS = 4096; 142 | else 143 | PLL_STEPS = 26624; 144 | end 145 | end 146 | %------------------------------------------ 147 | function [n_i,lfsr_state]= lr_fhss_get_next_freq_in_grid(enable_hop, lfsr_state, n_grid, polynomial, xoring_seed, hop_sequence_id ) 148 | sign=0; 149 | if enable_hop 150 | [n_i, lfsr_state] = lr_fhss_get_next_state( lfsr_state, n_grid, polynomial, xoring_seed ); 151 | else 152 | n_i = mod(hop_sequence_id, n_grid); 153 | end 154 | 155 | if n_i >= floor(bitsra(n_grid,1)) 156 | n_i = n_i - n_grid; 157 | end 158 | %n_i=uint16(n_i); 159 | end 160 | %------------------------------------------ 161 | -------------------------------------------------------------------------------- /LR-FHSS/lrfh_reconsig.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [result, para] = lrfh_reconsig(data, thischecksig_0, thisDataSegFreqHz, LRF_cfg) 6 | 7 | % 0. convert sig to baseband 8 | local_ant_num = size(thischecksig_0,1); 9 | thischecksig = lrfh_lpfsig(thischecksig_0,thisDataSegFreqHz,0,LRF_cfg); 10 | % task 0: about 0.025 sec for hdr on laptop on battery, no way to get better 11 | 12 | % 1. get the ideal phase change 13 | modsig0 = lrfh_gen_ideal_waveform(data,LRF_cfg); 14 | % task 1: very fast 15 | 16 | % 2. est the timing error 17 | maxbitnum = min(length(data),floor(size(thischecksig_0,2)/LRF_cfg.smblsmplnum)); 18 | smpltime = LRF_cfg.lookdist + 1 + round([0:maxbitnum-1]/LRF_cfg.BW*LRF_cfg.samplingrate); 19 | smplvals = thischecksig(:,smpltime); 20 | sigpower = sum(transpose(thischecksig(:,1:10:end).*conj(thischecksig(:,1:10:end)))); 21 | MRCcoeff = sigpower/sum(sigpower); 22 | 23 | phasechange = angle(smplvals(:,2:end)./smplvals(:,1:end-1)); 24 | movevals = zeros(1,local_ant_num); 25 | for ant=1:local_ant_num 26 | diff01 = []; diff10 = []; 27 | for didx=1:length(data)-3 % NOTE: skip some of the paddings 28 | if data(didx)==0 && data(didx+1)==1 29 | diff01 = [diff01, phasechange(ant, didx)]; 30 | elseif data(didx)==1 && data(didx+1)==0 31 | diff10 = [diff10, phasechange(ant, didx)]; 32 | end 33 | end 34 | % NOTE: 35 | % - 01 - and 10 +: sample too early; 36 | % - 01 + and 10 -: sample too late; 37 | movdir = 0; 38 | avg01 = mean(diff01); avg10 = mean(diff10); 39 | if avg01 < 0 && avg10 > 0 40 | movdir = 1; % too early, need to add 41 | elseif avg01 > 0 && avg10 < 0 42 | movdir = -1; % too late, need to sub 43 | end 44 | movphase = mean([abs(avg01), abs(avg10)]); 45 | movsampnum = round(movphase/2/(pi/2)*LRF_cfg.smblsmplnum); 46 | movevals(ant) = movdir*movsampnum; 47 | end 48 | estmove = sum(MRCcoeff.*movevals); 49 | estmove = sign(estmove)*min(LRF_cfg.max_SIC_time_off_smpl_num,abs(estmove)); % adding this does not change anything, but keep it here 50 | adjsmpltime = round(smpltime + estmove); 51 | if adjsmpltime(1) > 0 && adjsmpltime(end) < size(thischecksig,2) 52 | smpltime = adjsmpltime; 53 | end 54 | shifthere = round(LRF_cfg.smblsmplnum/2 - smpltime(1)); 55 | if shifthere > 0 56 | modsig0 = modsig0(shifthere:end); 57 | else 58 | modsig0 = [zeros(1,-shifthere) modsig0]; 59 | end 60 | para.timeadj = estmove; 61 | % fprintf(1,'--- estmove %.4f\n', estmove) 62 | % task 2: about 0.007 sec 63 | 64 | uselen = min(size(thischecksig,2),size(modsig0,2)); 65 | modsig = zeros(local_ant_num, size(modsig0,2)); 66 | 67 | fitseglen_default = round(LRF_cfg.smblsmplnum*LRF_cfg.recon_seg_len_symbol_num); % 10: 587; 16: 574 68 | fitsegnum = floor(uselen/fitseglen_default); 69 | if fitsegnum > 0 70 | allsmblnum = floor(uselen/LRF_cfg.smblsmplnum); 71 | eachsegnum = floor(allsmblnum/fitsegnum); 72 | spillover = allsmblnum - fitsegnum*eachsegnum; 73 | fitsegsmblenum = ones(1,fitsegnum)*eachsegnum; 74 | fitsegsmblenum(1:spillover) = fitsegsmblenum(1:spillover)+1; 75 | else 76 | fitsegnum = 1; 77 | fitsegsmblenum = [floor(uselen/LRF_cfg.smblsmplnum)]; 78 | end 79 | fitseglen = round(fitsegsmblenum/LRF_cfg.BW*LRF_cfg.samplingrate); 80 | % NOTE:10 symbols, ~20 ms, one cycle is already off by 50 Hz 81 | finetuneCFOarray_Hz = [-50:5:50]; 82 | usemodsig = zeros(size(modsig)); 83 | 84 | estgain = zeros(fitsegnum,local_ant_num); 85 | estcfo_Hz = zeros(fitsegnum,local_ant_num); 86 | for fitsegidx=1:fitsegnum 87 | if fitsegidx == 1 88 | thesesmblidx_bgn = 1; 89 | else 90 | thesesmblidx_bgn = sum(fitsegsmblenum(1:fitsegidx-1))+1; 91 | end 92 | thesesmblidx_end = thesesmblidx_bgn + fitsegsmblenum(fitsegidx) - 1; 93 | thesesmblidx = [thesesmblidx_bgn:thesesmblidx_end]; 94 | thesesmbllocs = smpltime(thesesmblidx); 95 | thisidealsmples = exp(1i*modsig0(thesesmbllocs)); 96 | thisrcvsmples = thischecksig(:, thesesmbllocs); 97 | 98 | finetuneCFOarray_cycle_num_seg = finetuneCFOarray_Hz*fitsegsmblenum(fitsegidx)*LRF_cfg.smblsmplnum/LRF_cfg.samplingrate; 99 | cmplxscores = zeros(local_ant_num,length(finetuneCFOarray_cycle_num_seg)); 100 | for ant=1:local_ant_num 101 | for fncfoidx=1:length(finetuneCFOarray_cycle_num_seg) 102 | thiscfoval = finetuneCFOarray_cycle_num_seg(fncfoidx); 103 | thiswave = exp(1i*2*pi*[0:fitsegsmblenum(fitsegidx)-1]/fitsegsmblenum(fitsegidx)*thiscfoval); 104 | tempp = thisrcvsmples(ant,:).*conj(thisidealsmples)./thiswave; 105 | cmplxscores(ant,fncfoidx) = sum(tempp)/length(tempp); 106 | end 107 | end 108 | scores = abs(cmplxscores); 109 | for ant=1:local_ant_num 110 | [a,b] = max(scores(ant,:)); 111 | estgain(fitsegidx,ant) = cmplxscores(ant,b); 112 | estcfo_Hz(fitsegidx,ant) = finetuneCFOarray_Hz(b); 113 | end 114 | end 115 | para.estgain = estgain; 116 | para.estcfo_Hz = estcfo_Hz; 117 | 118 | for ant=1:local_ant_num 119 | for fitsegidx=1:fitsegnum 120 | if fitsegidx == 1 121 | thislongbgn = 1; 122 | else 123 | thislongbgn = sum(fitseglen(1:fitsegidx-1))+1; 124 | end 125 | thislongend = thislongbgn + fitseglen(fitsegidx) - 1; 126 | thisidealsig = exp(1i*modsig0(thislongbgn:thislongend)); 127 | tempp = estcfo_Hz(fitsegidx,ant)*length(thisidealsig)/LRF_cfg.samplingrate; 128 | thiswave = exp(1i*2*pi*[0:length(thisidealsig)-1]/length(thisidealsig)*tempp); 129 | usemodsig(ant,thislongbgn:thislongend) = thisidealsig.*thiswave*exp(1i*angle(estgain(fitsegidx,ant))); 130 | end 131 | end 132 | if fitsegnum > 1 133 | midvalloc = round(cumsum(fitseglen) - fitseglen(1)/2); 134 | fitvalloc = [1 midvalloc sum(fitseglen)]; 135 | fitval = [abs(estgain(1,:)); abs(estgain(:,:)); abs(estgain(end,:))]; 136 | else 137 | fitvalloc = [1 sum(fitseglen)]; 138 | fitval = [abs(estgain(1,:)); abs(estgain(end,:))]; 139 | end 140 | fitx = [1:sum(fitseglen)]; 141 | fitestgainabs = ones(local_ant_num, length(fitx)); 142 | for ant=1:local_ant_num 143 | thisfitval = fitval(:,ant); 144 | fitestgainabs(ant,:) = interp1(fitvalloc, thisfitval, fitx); 145 | end 146 | usemodsig(:,1:size(fitestgainabs,2)) = usemodsig(:,1:size(fitestgainabs,2)).*fitestgainabs; 147 | 148 | uselen = min(size(usemodsig,2),size(thischecksig_0,2)); 149 | recondiff = thischecksig(:,1:uselen)-usemodsig(:,1:uselen); 150 | recondiffP = sum((recondiff.*conj(recondiff)),1); 151 | reconsigP = sum((usemodsig(:,1:uselen).*conj(usemodsig(:,1:uselen))),1); % Use recon sig because it has been filtered 152 | para.reconsnr = reconsigP(smpltime)./recondiffP(smpltime); 153 | 154 | %5. reapply cfo 155 | thisCFOwave = repmat(exp(1i*[0:size(thischecksig_0,2)-1]*2*pi/size(thischecksig_0,2)*thisDataSegFreqHz*(size(thischecksig_0,2)/LRF_cfg.samplingrate)),local_ant_num,1); 156 | result = zeros(size(thischecksig_0)); 157 | result(:,1:uselen) = usemodsig(:,1:uselen); 158 | result = result .* thisCFOwave; 159 | % task 5: about 0.01 sec -------------------------------------------------------------------------------- /LR-FHSS/lrfh_init_trellis.m: -------------------------------------------------------------------------------- 1 | LRF_cfg.myTrellis_header.numInputSymbols = 2; 2 | LRF_cfg.myTrellis_header.numOutputSymbols = 4; 3 | LRF_cfg.myTrellis_header.numStates = 16; 4 | LRF_cfg.myTrellis_header.nextStates = [0 1 ; ... 5 | 2 3 ; ... 6 | 4 5 ; ... 7 | 6 7 ; ... 8 | 8 9 ; ... 9 | 10 11 ; ... 10 | 12 13 ; ... 11 | 14 15 ; ... 12 | 0 1 ; ... 13 | 2 3 ; ... 14 | 4 5 ; ... 15 | 6 7 ; ... 16 | 8 9 ; ... 17 | 10 11 ; ... 18 | 12 13 ; ... 19 | 14 15 ]; 20 | LRF_cfg.myTrellis_header.outputs = [0 3 ; ... 21 | 1 2 ; ... 22 | 2 1 ; ... 23 | 3 0 ; ... 24 | 2 1 ; ... 25 | 3 0 ; ... 26 | 0 3 ; ... 27 | 1 2 ; ... 28 | 3 0 ; ... 29 | 2 1 ; ... 30 | 1 2 ; ... 31 | 0 3 ; ... 32 | 1 2 ; ... 33 | 0 3 ; ... 34 | 3 0 ; ... 35 | 2 1 ]; 36 | LRF_cfg.myTrellis_header.outputs_bin = cell(size(LRF_cfg.myTrellis_header.outputs)); 37 | for h=1:size(LRF_cfg.myTrellis_header.outputs,1) 38 | for hh=1:size(LRF_cfg.myTrellis_header.outputs,2) 39 | LRF_cfg.myTrellis_header.outputs_bin{h,hh} = de2bi(LRF_cfg.myTrellis_header.outputs(h,hh), 2, 'left-msb'); 40 | end 41 | end 42 | 43 | LRF_cfg.myTrellis.numInputSymbols = 2; 44 | LRF_cfg.myTrellis.numOutputSymbols = 8; 45 | LRF_cfg.myTrellis.numStates = 64; 46 | LRF_cfg.myTrellis.nextStates = [0 1 ; ... 47 | 2 3 ; ... 48 | 4 5 ; ... 49 | 6 7 ; ... 50 | 8 9 ; ... 51 | 10 11 ; ... 52 | 12 13 ; ... 53 | 14 15 ; ... 54 | 16 17 ; ... 55 | 18 19 ; ... 56 | 20 21 ; ... 57 | 22 23 ; ... 58 | 24 25 ; ... 59 | 26 27 ; ... 60 | 28 29 ; ... 61 | 30 31 ; ... 62 | 32 33 ; ... 63 | 34 35 ; ... 64 | 36 37 ; ... 65 | 38 39 ; ... 66 | 40 41 ; ... 67 | 42 43 ; ... 68 | 44 45 ; ... 69 | 46 47 ; ... 70 | 48 49 ; ... 71 | 50 51 ; ... 72 | 52 53 ; ... 73 | 54 55 ; ... 74 | 56 57 ; ... 75 | 58 59 ; ... 76 | 60 61 ; ... 77 | 62 63 ; ... 78 | 0 1 ; ... 79 | 2 3 ; ... 80 | 4 5 ; ... 81 | 6 7 ; ... 82 | 8 9 ; ... 83 | 10 11 ; ... 84 | 12 13 ; ... 85 | 14 15 ; ... 86 | 16 17 ; ... 87 | 18 19 ; ... 88 | 20 21 ; ... 89 | 22 23 ; ... 90 | 24 25 ; ... 91 | 26 27 ; ... 92 | 28 29 ; ... 93 | 30 31 ; ... 94 | 32 33 ; ... 95 | 34 35 ; ... 96 | 36 37 ; ... 97 | 38 39 ; ... 98 | 40 41 ; ... 99 | 42 43 ; ... 100 | 44 45 ; ... 101 | 46 47 ; ... 102 | 48 49 ; ... 103 | 50 51 ; ... 104 | 52 53 ; ... 105 | 54 55 ; ... 106 | 56 57 ; ... 107 | 58 59 ; ... 108 | 60 61 ; ... 109 | 62 63 ]; 110 | LRF_cfg.myTrellis.outputs = [0 7 ; ... 111 | 3 4 ; ... 112 | 7 0 ; ... 113 | 4 3 ; ... 114 | 6 1 ; ... 115 | 5 2 ; ... 116 | 1 6 ; ... 117 | 2 5 ; ... 118 | 1 6 ; ... 119 | 2 5 ; ... 120 | 6 1 ; ... 121 | 5 2 ; ... 122 | 7 0 ; ... 123 | 4 3 ; ... 124 | 0 7 ; ... 125 | 3 4 ; ... 126 | 4 3 ; ... 127 | 7 0 ; ... 128 | 3 4 ; ... 129 | 0 7 ; ... 130 | 2 5 ; ... 131 | 1 6 ; ... 132 | 5 2 ; ... 133 | 6 1 ; ... 134 | 5 2 ; ... 135 | 6 1 ; ... 136 | 2 5 ; ... 137 | 1 6 ; ... 138 | 3 4 ; ... 139 | 0 7 ; ... 140 | 4 3 ; ... 141 | 7 0 ; ... 142 | 7 0 ; ... 143 | 4 3 ; ... 144 | 0 7 ; ... 145 | 3 4 ; ... 146 | 1 6 ; ... 147 | 2 5 ; ... 148 | 6 1 ; ... 149 | 5 2 ; ... 150 | 6 1 ; ... 151 | 5 2 ; ... 152 | 1 6 ; ... 153 | 2 5 ; ... 154 | 0 7 ; ... 155 | 3 4 ; ... 156 | 7 0 ; ... 157 | 4 3 ; ... 158 | 3 4 ; ... 159 | 0 7 ; ... 160 | 4 3 ; ... 161 | 7 0 ; ... 162 | 5 2 ; ... 163 | 6 1 ; ... 164 | 2 5 ; ... 165 | 1 6 ; ... 166 | 2 5 ; ... 167 | 1 6 ; ... 168 | 5 2 ; ... 169 | 6 1 ; ... 170 | 4 3 ; ... 171 | 7 0 ; ... 172 | 3 4 ; ... 173 | 0 7 ]; -------------------------------------------------------------------------------- /LR-FHSS/lrfh_get_overlapinfo.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [FoundSigGird, outFoundDataSeg] = lrfh_get_overlapinfo(FoundDataSeg,use_grid_time_num,LRF_cfg) 6 | 7 | FoundSigGird = zeros(use_grid_time_num,LRF_cfg.use_grid_freq_num); 8 | 9 | fprintf(1,'getting overlapping info ... ') 10 | for pktidx=1:length(FoundDataSeg) 11 | thispkt = FoundDataSeg{pktidx}; 12 | thispkt.hdroverlapinfo = cell(1,thispkt.header_count); 13 | thispkt.overlapinfo = cell(1,thispkt.num_frags); 14 | for hdridx=1:thispkt.header_count 15 | thisbgn = thispkt.start - LRF_cfg.staysmplnum_hdr*(thispkt.header_count-hdridx+1) + LRF_cfg.lookdist + 1; 16 | thisend = thisbgn + LRF_cfg.staysmplnum_hdr - 1; 17 | thistimeidxbgn = ceil(thisbgn/LRF_cfg.use_grid_time_sampnum); 18 | thistimeidxend = floor(thisend/LRF_cfg.use_grid_time_sampnum); 19 | thisfreqidx = round(thispkt.freq_header_Hz(hdridx) / LRF_cfg.use_grid_freq_Hz) + LRF_cfg.use_grid_freq_idx_offset + ceil(LRF_cfg.use_grid_freq_num/2); 20 | if min(thisfreqidx) > 0 && max(thisfreqidx) <= LRF_cfg.use_grid_freq_num 21 | FoundSigGird(thistimeidxbgn:thistimeidxend,thisfreqidx) = FoundSigGird(thistimeidxbgn:thistimeidxend,thisfreqidx) + thispkt.estpower; 22 | end 23 | thispkt.hdroverlapinfo{hdridx}.thistimeidx = [thistimeidxbgn:thistimeidxend]; 24 | thispkt.hdroverlapinfo{hdridx}.thisfreqidx = thisfreqidx; 25 | thispkt.hdroverlapinfo{hdridx}.interf_flag_mat = zeros(length(thispkt.hdroverlapinfo{hdridx}.thistimeidx),length(thispkt.hdroverlapinfo{hdridx}.thisfreqidx)); 26 | if hdridx == 1 27 | thispkt.firstsmplidx = thistimeidxbgn; 28 | end 29 | end 30 | for fgidx=1:thispkt.num_frags 31 | thisbgn = thispkt.start + LRF_cfg.staysmplnum_data * (fgidx-1); 32 | thisend = thisbgn + LRF_cfg.staysmplnum_data - 1; % NOTE: do not know the length of the last fragment, use maximum 33 | thistimeidxbgn = round(thisbgn/LRF_cfg.use_grid_time_sampnum); 34 | thistimeidxend = round(thisend/LRF_cfg.use_grid_time_sampnum); 35 | thisfreqidx = round(thispkt.freq_Hz(fgidx) / LRF_cfg.use_grid_freq_Hz) + LRF_cfg.use_grid_freq_idx_offset + ceil(LRF_cfg.use_grid_freq_num/2); 36 | if min(thisfreqidx) > 0 && max(thisfreqidx) <= LRF_cfg.use_grid_freq_num 37 | FoundSigGird(thistimeidxbgn:thistimeidxend,thisfreqidx) = FoundSigGird(thistimeidxbgn:thistimeidxend,thisfreqidx) + thispkt.estpower; 38 | end 39 | thispkt.overlapinfo{fgidx}.thistimeidx = [thistimeidxbgn:thistimeidxend]; 40 | thispkt.overlapinfo{fgidx}.thisfreqidx = thisfreqidx; 41 | thispkt.overlapinfo{fgidx}.interf_flag_mat = zeros(length(thispkt.overlapinfo{fgidx}.thistimeidx),length(thispkt.overlapinfo{fgidx}.thisfreqidx)); 42 | if fgidx == thispkt.num_frags 43 | thispkt.lastsmplidx = thistimeidxend; 44 | end 45 | end 46 | FoundDataSeg{pktidx} = thispkt; 47 | end 48 | 49 | for pktidx=1:length(FoundDataSeg) 50 | thispkt = FoundDataSeg{pktidx}; 51 | for hdridx=1:thispkt.header_count 52 | thistimeidx = thispkt.hdroverlapinfo{hdridx}.thistimeidx; 53 | thisfreqidx = thispkt.hdroverlapinfo{hdridx}.thisfreqidx; 54 | if min(thisfreqidx) > 0 && max(thisfreqidx) <= LRF_cfg.use_grid_freq_num 55 | for tidxidx=1:length(thistimeidx) 56 | for fidxidx=1:length(thisfreqidx) 57 | tidx = thistimeidx(tidxidx); 58 | fidx = thisfreqidx(fidxidx); 59 | if FoundSigGird(tidx,fidx) > thispkt.estpower * LRF_cfg.interf_alarm_thresh 60 | thispkt.hdroverlapinfo{hdridx}.interf_flag_mat(tidxidx,fidxidx) = 1; 61 | end 62 | end 63 | end 64 | end 65 | end 66 | for fgidx=1:thispkt.num_frags 67 | thistimeidx = thispkt.overlapinfo{fgidx}.thistimeidx; 68 | thisfreqidx = thispkt.overlapinfo{fgidx}.thisfreqidx; 69 | if min(thisfreqidx) > 0 && max(thisfreqidx) <= LRF_cfg.use_grid_freq_num 70 | for tidxidx=1:length(thistimeidx) 71 | for fidxidx=1:length(thisfreqidx) 72 | tidx = thistimeidx(tidxidx); 73 | fidx = thisfreqidx(fidxidx); 74 | if FoundSigGird(tidx,fidx) > thispkt.estpower * LRF_cfg.interf_alarm_thresh 75 | thispkt.overlapinfo{fgidx}.interf_flag_mat(tidxidx,fidxidx) = 1; 76 | end 77 | end 78 | end 79 | end 80 | end 81 | thisdetailedcolinfo = []; 82 | for fgidx=1:thispkt.num_frags 83 | thistimeidx = thispkt.overlapinfo{fgidx}.thistimeidx; 84 | thiscenterf = thispkt.freq_Hz(fgidx); 85 | thisinterfP = zeros(1,length(thistimeidx)); 86 | for pktidx2=1:length(FoundDataSeg) 87 | if pktidx == pktidx2 88 | continue; 89 | end 90 | thatpkt = FoundDataSeg{pktidx2}; 91 | if thistimeidx(1) > thatpkt.lastsmplidx || thistimeidx(end) < thatpkt.firstsmplidx 92 | continue; 93 | end 94 | for hdrdataidx=1:2 95 | for segidx=1:100 96 | if hdrdataidx == 1 97 | if segidx <= thatpkt.header_count 98 | thattimeidx = thatpkt.hdroverlapinfo{segidx}.thistimeidx; 99 | thatcenterf = thatpkt.freq_header_Hz(segidx); 100 | else 101 | break; 102 | end 103 | else 104 | if segidx <= thatpkt.num_frags 105 | thattimeidx = thatpkt.overlapinfo{segidx}.thistimeidx; 106 | thatcenterf = thatpkt.freq_Hz(segidx); 107 | else 108 | break; 109 | end 110 | end 111 | overlaptimeidx = intersect(thistimeidx,thattimeidx); 112 | interffdist = thatcenterf - thiscenterf; 113 | if length(overlaptimeidx) > 0 && interffdist > LRF_cfg.InterfF(1) && interffdist < LRF_cfg.InterfF(end) 114 | tempp = interffdist - LRF_cfg.InterfF; 115 | [a,interffidx] = min(abs(tempp)); 116 | overlapinfgidx = overlaptimeidx - thistimeidx(1) + 1; 117 | overlapinthatfgidx = overlaptimeidx - thattimeidx(1) + 1; 118 | thatpktinterP = LRF_cfg.InterfP(interffidx)*thatpkt.estpower; 119 | thisinterfP(overlapinfgidx) = thisinterfP(overlapinfgidx) + thatpktinterP; 120 | 121 | hereidx = length(thisdetailedcolinfo)+1; 122 | thisdetailedcolinfo{hereidx}.own_fgidx = fgidx; 123 | thisdetailedcolinfo{hereidx}.col_pkt_idx = pktidx2; 124 | thisdetailedcolinfo{hereidx}.col_pkt_hdrorfg = hdrdataidx; % 1: header, 2: data frag 125 | thisdetailedcolinfo{hereidx}.col_pkt_segidx = segidx; 126 | thisdetailedcolinfo{hereidx}.col_pkt_symidx = overlapinthatfgidx; 127 | thisdetailedcolinfo{hereidx}.overlapinfgidx = overlapinfgidx; 128 | thisdetailedcolinfo{hereidx}.interP = thatpktinterP; 129 | end 130 | end 131 | end 132 | end 133 | thispkt.overlapinfo{fgidx}.invSIR = thisinterfP/thispkt.estpower; 134 | end 135 | thispkt.detailedcolinfo = thisdetailedcolinfo; 136 | FoundDataSeg{pktidx} = thispkt; 137 | end 138 | outFoundDataSeg = FoundDataSeg; 139 | fprintf(1,'done\n') -------------------------------------------------------------------------------- /LR-FHSS/lrfh_detect_hdr.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [SegCoarseTime, SegCoarseFreqHz, SegCoarseScore] = lrfh_detect_hdr(LRFHSS_time_sig,LRF_cfg) 6 | 7 | SegCoarseTime = []; SegCoarseFreqHz = []; SegCoarseScore = []; 8 | 9 | local_print_flag = 1; 10 | if local_print_flag 11 | fprintf(1, 'lrfh sim detecting header ...\n') 12 | end 13 | 14 | % 0. set up 15 | zvl_sync_peak_thresh_coef = 2; % NOTE: has been 2 16 | zvl_peak_num_take_max = 100; 17 | T = LRF_cfg.sync_scan_step; 18 | scannsamechanwidth = ceil(LRF_cfg.BW * LRF_cfg.sync_scan_step / LRF_cfg.samplingrate); % NOTE: better be an odd number 19 | maxpeakconsecutivenum = ceil(LRF_cfg.staytime_hdr*LRF_cfg.samplingrate/T); 20 | threshpeakconsecutivenum = maxpeakconsecutivenum - 1; 21 | zvl_fdsigspan_coarse = round(LRF_cfg.BW*0.5*LRF_cfg.sync_scan_seconds); % NOTE: the peak loc does not drift more than 244 Hz 22 | zvl_fdsigspan_fine = round(LRF_cfg.BW*LRF_cfg.staytime_hdr); 23 | usewindow = gausswin(scannsamechanwidth); 24 | usewindow_half = round((scannsamechanwidth-1)/2); 25 | finescanstep = LRF_cfg.smblsmplnum; 26 | finesiglen = LRF_cfg.finesiglen; 27 | local_ant_num = size(LRFHSS_time_sig,1); 28 | tempp = LRF_cfg.synccheckpeakrangehalf; 29 | local_check_sync_idx = [T-tempp:T,1:tempp]; 30 | local_check_sync_exclude_idx = setdiff([1:T],local_check_sync_idx); 31 | 32 | % 1. computing the segment FFTs 33 | sync_sigvec_flat = zeros(1,floor(size(LRFHSS_time_sig,2)/T)*T); 34 | thisbgn = 1; 35 | while thisbgn < size(LRFHSS_time_sig,2) - T 36 | thisend = thisbgn + T - 1; 37 | sync_sigvec_flat(thisbgn:thisend) = lrfh_cal_fft_squared(LRFHSS_time_sig(:,thisbgn:thisend)); 38 | thisbgn = thisbgn + T; 39 | end 40 | sync_sigvec = reshape(sync_sigvec_flat, T, length(sync_sigvec_flat)/T)'; 41 | dbg_save_sync_sigvec = sync_sigvec; 42 | for h=1:size(sync_sigvec,1) 43 | tempp = [sync_sigvec(h,:), sync_sigvec(h,1:usewindow_half)]; 44 | tempp1 = filter(usewindow,1,tempp); 45 | sync_sigvec(h,:) = tempp1(usewindow_half+1:end); 46 | sync_sigvec(h,local_check_sync_exclude_idx) = 0; 47 | end 48 | sync_rcd = cell(1,size(sync_sigvec,1)); 49 | for h=1:length(sync_rcd) 50 | [a,b] = peakfinder(sync_sigvec(h,:), median(sync_sigvec(h,local_check_sync_idx))*zvl_sync_peak_thresh_coef); 51 | if length(a) 52 | [tempp,bb] = sort(b, 'descend'); aa = a(bb); 53 | takenum = min(length(aa),zvl_peak_num_take_max); 54 | sync_rcd{h}.peakloc = aa(1:takenum); 55 | sync_rcd{h}.peakhei = tempp(1:takenum); 56 | else 57 | sync_rcd{h}.peakloc = []; 58 | sync_rcd{h}.peakhei = []; 59 | end 60 | end 61 | save_sync_rcd = sync_rcd; 62 | if local_print_flag 63 | fprintf(1, ' 1. getting segment FFT done, window size %.3f sec\n', LRF_cfg.sync_scan_seconds) 64 | end 65 | 66 | 67 | % 2. finding initial candidates based only on 5 consecutive peak criterion 68 | candiates = []; 69 | for symidx=1:length(sync_rcd) - maxpeakconsecutivenum*2 70 | for peakidx=1:length(sync_rcd{symidx}.peakloc) 71 | thisloc = sync_rcd{symidx}.peakloc(peakidx); 72 | thishei = sync_rcd{symidx}.peakhei(peakidx); 73 | thisrange = mod(thisloc + [-zvl_fdsigspan_coarse:zvl_fdsigspan_coarse] - 1, T) + 1; 74 | hist = zeros(maxpeakconsecutivenum,3); 75 | hist(1,1) = thisloc; 76 | hist(1,2) = thishei; 77 | hist(1,3) = peakidx; 78 | for ridx=2:maxpeakconsecutivenum 79 | symidx2 = symidx + ridx - 1; 80 | for peakidx2=1:length(sync_rcd{symidx2}.peakloc) 81 | thatloc = sync_rcd{symidx2}.peakloc(peakidx2); 82 | thathei = sync_rcd{symidx2}.peakhei(peakidx2); 83 | if length(find(thisrange == thatloc)) > 0 84 | hist(ridx,1) = thatloc; 85 | hist(ridx,2) = thathei; 86 | hist(ridx,3) = peakidx2; 87 | end 88 | end 89 | end 90 | if length(find(hist(:,1))) >= threshpeakconsecutivenum 91 | addidx = length(candiates) + 1; 92 | candiates{addidx}.bgnsym = symidx; 93 | candiates{addidx}.range = thisrange; 94 | candiates{addidx}.peakloc = thisloc; 95 | candiates{addidx}.hist = hist; 96 | candiates{addidx}.thisest = (symidx - 1)*T + 1; 97 | vaildidx = find(hist(:,1)); 98 | alllocs = hist(vaildidx,1); 99 | tempp = find(alllocs > T/2); 100 | alllocs(tempp) = alllocs(tempp) - T; 101 | thisfreq = mean(alllocs); 102 | candiates{addidx}.thisfreq_Hz = thisfreq*LRF_cfg.samplingrate/T; 103 | % abs(thisfreq - 2115) < 6 && abs(candiates{addidx}.thisest - 409032) < 1000 104 | 105 | for ridx=2:2 %maxpeakconsecutivenum 106 | % NOTE: just need to remove the second one to break the 107 | % 4-5 run, so that the rest cannnot be identified as a 108 | % potential header. better when there is some collision 109 | % when some data seg is right before the header 110 | symidx2 = symidx + ridx - 1; 111 | maskidx = hist(ridx,3); 112 | if maskidx > 0 113 | sync_rcd{symidx2}.peakloc(maskidx) = []; 114 | sync_rcd{symidx2}.peakhei(maskidx) = []; 115 | end 116 | end 117 | end 118 | end 119 | end 120 | if local_print_flag 121 | fprintf(1, ' 2. finding candidate (at least %d peaks at same loc): got %d candidates\n', threshpeakconsecutivenum, length(candiates)); 122 | end 123 | 124 | % 3. sliding window one symbol at a time to check when the energy 125 | % withint the rage is the highest 126 | 127 | if local_print_flag 128 | fprintf(1, ' 3. finding coarse estimates: candi '); 129 | for h=1:5 fprintf(1, ' '); end 130 | end 131 | for candidx=1:length(candiates) 132 | if local_print_flag 133 | if mod(candidx,10) == 0 134 | for h=1:5 fprintf(1, '\b'); end 135 | fprintf(1, '%5d', candidx); 136 | end 137 | end 138 | 139 | findscanbgn = candiates{candidx}.thisest - T + 1; 140 | findscanend = candiates{candidx}.thisest + T; 141 | here_scanlen_sec = (findscanend-findscanbgn+1)/LRF_cfg.samplingrate; 142 | here_scanlen_num = floor(here_scanlen_sec*LRF_cfg.BW); 143 | findscanarray = 1+round([0:here_scanlen_num-1]/LRF_cfg.BW*LRF_cfg.samplingrate); 144 | 145 | thisfreq_Hz = candiates{candidx}.thisfreq_Hz; 146 | SegCoarseTime(candidx,1:2) = [candiates{candidx}.thisest,candiates{candidx}.thisest+finesiglen-1]; 147 | SegCoarseFreqHz(candidx) = thisfreq_Hz; 148 | SegCoarseScore(candidx) = 0; 149 | if ~(findscanbgn > 0 && findscanend < size(LRFHSS_time_sig,2)) 150 | continue; 151 | end 152 | 153 | thissig = LRFHSS_time_sig(:,findscanbgn:findscanend + finesiglen); 154 | herepadsmplnum = 8; 155 | pad_thissig = horzcat(thissig(:,1:herepadsmplnum*LRF_cfg.smblsmplnum),thissig); 156 | thislpfsig = lrfh_lpfsig(pad_thissig,thisfreq_Hz,0,LRF_cfg); 157 | thislpfsigp = thislpfsig.*conj(thislpfsig); 158 | if local_ant_num > 1 159 | thislpfsigp = sum(thislpfsigp); 160 | end 161 | thislpfsigp = thislpfsigp(:,herepadsmplnum*LRF_cfg.smblsmplnum+1:end); 162 | 163 | segnum = floor(length(thislpfsigp)/finescanstep); 164 | tempp = thislpfsigp(1:segnum*finescanstep); 165 | tempp1 = reshape(tempp,finescanstep,segnum); 166 | segval = sum(tempp1); 167 | 168 | winsegnum = round(finesiglen/finescanstep); 169 | scores = zeros(1,length(findscanarray)); 170 | for scanidx=1:length(findscanarray) 171 | scores(scanidx) = sum(segval(scanidx:min(scanidx+winsegnum-1,segnum))); 172 | end 173 | [heremaxscore,heremaxloc] = max(scores); 174 | 175 | shouldkeepflag = 1; 176 | normscores = scores/heremaxscore; 177 | if mean(abs(normscores-mean(normscores))) < 0.01 178 | shouldkeepflag = 0; 179 | end 180 | 181 | if shouldkeepflag 182 | SegCoarseTime(candidx,:) = findscanarray(heremaxloc) + findscanbgn + [0,finesiglen-1]; 183 | SegCoarseFreqHz(candidx) = thisfreq_Hz; 184 | SegCoarseScore(candidx) = heremaxscore; 185 | end 186 | end 187 | 188 | rmvflag = zeros(1,size(SegCoarseTime,1)); 189 | rmvflag(find(SegCoarseScore==0)) = 1; 190 | for h=1:size(SegCoarseTime,1) 191 | tempp1 = abs(SegCoarseTime(h,1) - SegCoarseTime(:,1)); 192 | tempp1(h) = max(tempp1); 193 | tempp2 = abs(SegCoarseFreqHz(h) - SegCoarseFreqHz); 194 | tempp2(h) = max(tempp2); 195 | tempp3 = find(tempp1 < LRF_cfg.smblsmplnum*3); 196 | tempp4 = find(tempp2 < 10); 197 | tempp5 = intersect(tempp3, tempp4); 198 | tempp6 = find(SegCoarseScore < SegCoarseScore(h)*0.999); 199 | tempp7 = intersect(tempp5, tempp6); 200 | rmvflag(tempp7) = 1; 201 | end 202 | rmvlist = find(rmvflag); 203 | SegCoarseTime(rmvlist,:) = []; 204 | SegCoarseFreqHz(rmvlist) = []; 205 | SegCoarseScore(rmvlist) = []; 206 | 207 | if local_print_flag 208 | fprintf(1, '\n found %d headers to further process\n', length(SegCoarseTime)); 209 | end -------------------------------------------------------------------------------- /LR-FHSS/lrfh_decode_found_pkts.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [outFoundDataSeg, LRFHSS_recon_time_sig] = lrfh_decode_found_pkts(FoundDataSeg,LRFHSS_time_sig,LRF_cfg) 6 | 7 | LRFHSS_recon_time_sig = zeros(size(LRFHSS_time_sig)); 8 | save_reconsig_flag = (length(FoundDataSeg) <= 2) && LRF_cfg.check_SIC_flag; 9 | orig_LRFHSS_time_sig = LRFHSS_time_sig; 10 | 11 | allpktpower = zeros(1,length(FoundDataSeg)); 12 | for pktidx=1:length(FoundDataSeg) 13 | % fprintf(1, 'pkt %d: %f\n', pktidx, FoundDataSeg{pktidx}.estpower) 14 | allpktpower(pktidx) = FoundDataSeg{pktidx}.estpower; 15 | end 16 | [a,decodeorder] = sort(allpktpower, 'descend'); 17 | trieddecoding_flag = zeros(1,length(decodeorder)); 18 | 19 | reconphdrsig = cell(1,length(decodeorder)); % reconphdrsig{pktidx}: hdr sig for packet pktidx 20 | for pktidxidx=1:length(decodeorder) 21 | thishdrreconsig = zeros(size(LRFHSS_time_sig)); 22 | thisreconstart = size(LRFHSS_time_sig,2); 23 | thisreconend = 1; 24 | pktidx = decodeorder(pktidxidx); 25 | thispkt = FoundDataSeg{pktidx}; 26 | thishdrsdata = zeros(thispkt.header_count,40); 27 | gotflag = zeros(1,thispkt.header_count); 28 | gotstate = zeros(1,thispkt.header_count); 29 | for hdridx=1:thispkt.header_count 30 | if ~isempty(thispkt.headerinfo{hdridx}) 31 | thishdrsdata(hdridx,:) = thispkt.headerinfo{hdridx}.decode; 32 | gotflag(hdridx) = 1; 33 | gotstate(hdridx) = thispkt.headerinfo{hdridx}.info.state-1; 34 | end 35 | end 36 | if sum(gotflag) < thispkt.header_count 37 | gotidx = find(gotflag); 38 | vals = thishdrsdata(gotidx(1),:); 39 | missidx = find(gotflag == 0); 40 | for zzz=1:length(missidx) 41 | thishdridx = missidx(zzz); 42 | thishdrsdata(thishdridx,:) = vals; 43 | thishdrrevidx = thispkt.header_count - thishdridx + 1; 44 | if thishdrrevidx == 3 45 | syncbits = [1 0]; 46 | elseif thishdrrevidx == 2 47 | syncbits = [0 1]; 48 | else 49 | syncbits = [0 0]; 50 | end 51 | thishdrsdata(thishdridx,29:30) = syncbits; 52 | tempp = lrfh_crc8(thishdrsdata(thishdridx,1:32)); 53 | thishdrsdata(thishdridx,33:end) = tempp; 54 | end 55 | end 56 | for hdridx=1:thispkt.header_count 57 | if hdridx == 1 58 | hereaddnum = 3; 59 | hereaddpad = [0 0 0]; 60 | hereaddpad_tail = [0]; 61 | else 62 | hereaddnum = 0; 63 | hereaddpad = []; 64 | hereaddpad_tail = [0]; 65 | end 66 | thisbgn = thispkt.start - (thispkt.header_count - hdridx + 1) * LRF_cfg.staysmplnum_hdr - LRF_cfg.smblsmplnum * hereaddnum; 67 | thisend = thisbgn + LRF_cfg.staysmplnum_hdr - 1 + LRF_cfg.smblsmplnum * (hereaddnum+1); 68 | orig_sig = LRFHSS_time_sig(:, thisbgn:thisend); 69 | thisreconbits = lrfh_gen_hdr_bits(gotstate(hdridx), thishdrsdata(hdridx,:), LRF_cfg, gotflag(hdridx)); 70 | [thisreconfragsig, thispara] = lrfh_reconsig([hereaddpad,thisreconbits,hereaddpad_tail], orig_sig, thispkt.freq_header_Hz(hdridx), LRF_cfg); 71 | if hdridx == 1 72 | thisreconfragsig(:,1:LRF_cfg.smblsmplnum*2) = 0; 73 | end 74 | cuttaillen = LRF_cfg.smblsmplnum*1; 75 | thishdrreconsig(:,thisbgn:thisend-cuttaillen) = thisreconfragsig(:,1:end-cuttaillen); 76 | thispkt.hdrreconinfo{hdridx} = thispara; 77 | thisreconstart = min(thisreconstart, thisbgn); 78 | thisreconend = max(thisreconend, thisend); 79 | end 80 | reconphdrsig{pktidx}.start = thisreconstart; 81 | reconphdrsig{pktidx}.end = thisreconend; 82 | reconphdrsig{pktidx}.sig = thishdrreconsig(:,thisreconstart:thisreconend); 83 | LRFHSS_time_sig = LRFHSS_time_sig - thishdrreconsig; 84 | FoundDataSeg{pktidx} = thispkt; 85 | end 86 | 87 | for pktidxidx=1:length(decodeorder) 88 | pktidx = decodeorder(pktidxidx); 89 | trieddecoding_flag(pktidx) = 1; 90 | 91 | thispkt = FoundDataSeg{pktidx}; 92 | thispkt.CRCpass = 0; 93 | thispkt.decoderes = []; 94 | 95 | thisFragment = []; data = []; GMSKdemodvaladj = []; 96 | thiscolinfo = thispkt.detailedcolinfo; 97 | for fragmentidx=1:thispkt.num_frags 98 | thisFrgTime = thispkt.SegCoarseTime(fragmentidx, :); 99 | herepassoverlapinfo = thispkt.overlapinfo{fragmentidx}; 100 | thisinterfP = zeros(1,length(thispkt.overlapinfo{fragmentidx}.thistimeidx)); 101 | for h=1:length(thiscolinfo) 102 | if thiscolinfo{h}.own_fgidx == fragmentidx 103 | pktidx2 = thiscolinfo{h}.col_pkt_idx; 104 | pkt2_hdrdataidx = thiscolinfo{h}.col_pkt_hdrorfg; % 1: header, 2: data frag 105 | pkt2_segidx = thiscolinfo{h}.col_pkt_segidx; 106 | pkt2_overlapinthatfgidx = thiscolinfo{h}.col_pkt_symidx; 107 | overlapinfgidx = thiscolinfo{h}.overlapinfgidx; 108 | interP = thiscolinfo{h}.interP; 109 | pkt2_para = []; 110 | if pkt2_hdrdataidx == 1 111 | pkt2_para = FoundDataSeg{pktidx2}.hdrreconinfo{pkt2_segidx}; 112 | else 113 | if FoundDataSeg{pktidx2}.CRCpass 114 | pkt2_para = FoundDataSeg{pktidx2}.datareconinfo{pkt2_segidx}; 115 | end 116 | end 117 | if ~isempty(pkt2_para) 118 | if 1 % no differnece, still use this one because morally correct 119 | srnvals = pkt2_para.reconsnr(intersect(pkt2_overlapinthatfgidx,[1:length(pkt2_para.reconsnr)])); 120 | srnvals(srnvals>10) = 10; 121 | usesnr = mean(srnvals); 122 | otherP = interP/usesnr; 123 | adjinterP = otherP/LRF_cfg.recon_seg_len_symbol_num; 124 | else 125 | adjinterP = interP/LRF_cfg.recon_seg_len_symbol_num; 126 | end 127 | else 128 | adjinterP = interP; 129 | end 130 | thisinterfP(overlapinfgidx) = thisinterfP(overlapinfgidx) + adjinterP; 131 | end 132 | herepassoverlapinfo.invSIR = thisinterfP/thispkt.estpower; % NOTE: just need to update this one, other fields not needed 133 | end 134 | [thisFragment{fragmentidx}, GMSKdemodvaladj(fragmentidx)] = lrfh_demodulate_payload(LRFHSS_time_sig, thisFrgTime, thispkt.freq_Hz(fragmentidx), LRF_cfg, thispkt.num_bits_frags(fragmentidx)+1, herepassoverlapinfo, thispkt.estpower, thispkt.CR); 135 | if length(thisFragment{fragmentidx}.bits) ~= thispkt.num_bits_frags(fragmentidx)+2 136 | data = [data thisFragment{fragmentidx}.bits(2:end)]; 137 | else 138 | data = [data thisFragment{fragmentidx}.bits(3:end)]; 139 | end 140 | end 141 | if ~(length(data) <= 66 && thispkt.CR == 3) 142 | data(isnan(data)) = 0; 143 | thisdecoderes.bits = data; 144 | thisdecoderes.deint = lrfh_deinterleaving_payload(thisdecoderes.bits, thispkt.data_in_bitcount); 145 | [a,b,c] = lrfh_decode_payload(thisdecoderes.deint, thispkt.CR, LRF_cfg); 146 | thisdecoderes.decode = a; 147 | thisdecoderes.CRCpass = b; 148 | thisdecoderes.cost = c; 149 | thisdecoderes.dewhitening = lrfh_dewhitening_payload(thisdecoderes.decode,length(thisdecoderes.decode)/8); 150 | tempp = reshape(thisdecoderes.dewhitening,8,length(thisdecoderes.dewhitening)/8)'; 151 | thisdecoderes.foundpayload = bi2de(tempp,"left-msb"); 152 | 153 | thispkt.CRCpass = thisdecoderes.CRCpass; 154 | thispkt.decoderes = thisdecoderes; 155 | thispkt.dataGMSKdemodvaladj = GMSKdemodvaladj; 156 | end 157 | 158 | if thispkt.CRCpass 159 | fprintf(1,'pkt %d: CR %d, CRC PASS, cost %.2f, data [', pktidx, thispkt.CR, thispkt.decoderes.cost); 160 | for h=1:length(thispkt.decoderes.foundpayload) 161 | fprintf(1, '%.2x', thispkt.decoderes.foundpayload(h)); 162 | if h < length(thispkt.decoderes.foundpayload) 163 | fprintf(1,', '); 164 | else 165 | fprintf(1,'], '); 166 | end 167 | end 168 | fprintf(1,'freq adj header ['); 169 | for h=1:length(thispkt.headerinfo) 170 | if ~isempty(thispkt.headerinfo{h}) 171 | fprintf(1, '%.2f', thispkt.headerinfo{h}.GMSKdemodvaladj); 172 | else 173 | fprintf(1, '-'); 174 | end 175 | if h < length(thispkt.headerinfo) 176 | fprintf(1,', '); 177 | else 178 | fprintf(1,'], '); 179 | end 180 | end 181 | fprintf(1,'data ['); 182 | for h=1:length(thispkt.dataGMSKdemodvaladj) 183 | fprintf(1, '%.2f', thispkt.dataGMSKdemodvaladj(h)); 184 | if h < length(thispkt.dataGMSKdemodvaladj) 185 | fprintf(1,', '); 186 | else 187 | fprintf(1,']\n'); 188 | end 189 | end 190 | else 191 | fprintf(1,'pkt %d: CRC FAIL\n', pktidx); 192 | end 193 | 194 | if thispkt.CRCpass 195 | reconfrags = lrfh_gen_data_frag_bits(thispkt,LRF_cfg); 196 | recondatasig = zeros(size(LRFHSS_time_sig,1), length(reconfrags)*LRF_cfg.staysmplnum_data); 197 | hereaddnum_head = 0; hereaddnum_tail = 0; 198 | for fgidx=1:length(reconfrags) 199 | orig_sig = LRFHSS_time_sig(:, thispkt.SegCoarseTime(fgidx,1)-hereaddnum_head*LRF_cfg.smblsmplnum:thispkt.SegCoarseTime(fgidx,2)+hereaddnum_tail*LRF_cfg.smblsmplnum); 200 | [thisreconfragsig, thispara] = lrfh_reconsig([reconfrags{fgidx}.bits(1:end)], orig_sig, thispkt.freq_Hz(fgidx), LRF_cfg); 201 | thisbgn = thispkt.SegCoarseTime(fgidx,1)-thispkt.start+1; 202 | thisend = thisbgn + size(thisreconfragsig,2) - 1; 203 | recondatasig(:,thisbgn:thisend) = thisreconfragsig; 204 | thispkt.datareconinfo{fgidx} = thispara; 205 | end 206 | thisbgn = thispkt.start - hereaddnum_head*LRF_cfg.smblsmplnum; 207 | thisend = thisbgn + size(recondatasig,2) - 1; 208 | thisreconsig = zeros(size(LRFHSS_time_sig)); 209 | thisreconsig(:,thisbgn:thisend) = recondatasig; 210 | LRFHSS_time_sig = LRFHSS_time_sig - thisreconsig; 211 | thisreconsig(:,reconphdrsig{pktidx}.start:reconphdrsig{pktidx}.end) = reconphdrsig{pktidx}.sig; 212 | LRFHSS_recon_time_sig = LRFHSS_recon_time_sig + thisreconsig; 213 | if save_reconsig_flag 214 | thispkt.reconsig = thisreconsig; 215 | thispkt.reconstart = reconphdrsig{pktidx}.start; 216 | thispkt.reconend = thisend; 217 | end 218 | end 219 | FoundDataSeg{pktidx} = thispkt; 220 | 221 | end 222 | outFoundDataSeg = FoundDataSeg; -------------------------------------------------------------------------------- /LR-FHSS/lrfh_detect_pkt.m: -------------------------------------------------------------------------------- 1 | % Copyright (C) 2025 2 | % Florida State University 3 | % All Rights Reserved 4 | 5 | function [HeaderOutput,FoundDataSeg] = lrfh_detect_pkt(lrfh_sim_decode_try_count,LRFHSS_time_sig,lrfh_sim_result,LRF_cfg) 6 | 7 | dbg_test_hdr_decoding_flag = 0; 8 | if dbg_test_hdr_decoding_flag == 0 9 | [SegCoarseTime, SegCoarseFreqHz, SegCoarseScore] = lrfh_detect_hdr(LRFHSS_time_sig,LRF_cfg); 10 | else 11 | SegCoarseTime = zeros(length(idealFoundDataSeg{1}.headerinfo),2); 12 | SegCoarseFreqHz = zeros(1,length(idealFoundDataSeg{1}.headerinfo)); 13 | end 14 | 15 | if lrfh_sim_decode_try_count > 1 && length(SegCoarseFreqHz) > 0 16 | % NOTE: run decoding only twice, so just check the last one 17 | rmvflag = zeros(1, length(SegCoarseFreqHz)); 18 | for lastpktidx=1:length(lrfh_sim_result{lrfh_sim_decode_try_count-1}) 19 | thatpkt = lrfh_sim_result{lrfh_sim_decode_try_count-1}{lastpktidx}; 20 | if thatpkt.CRCpass 21 | for hdridx=1:thatpkt.header_count 22 | thishdr = thatpkt.headerinfo{hdridx}; 23 | if ~isempty(thishdr) 24 | thishdrctime = thishdr.CoarseTime(1); 25 | thishdrcfreq = thishdr.CoarseFreqHz; 26 | tempp1 = abs(SegCoarseTime(:,1) - thishdrctime); 27 | tempp2 = abs(SegCoarseFreqHz - thishdrcfreq); 28 | tempp3 = find(tempp1 < LRF_cfg.smblsmplnum*3); 29 | tempp4 = find(tempp2 < 10); 30 | tempp5 = intersect(tempp3, tempp4); 31 | rmvflag(tempp5) = 1; 32 | end 33 | end 34 | end 35 | end 36 | rmvlist = find(rmvflag); 37 | SegCoarseTime(rmvlist,:) = []; 38 | SegCoarseFreqHz(rmvlist) = []; 39 | end 40 | 41 | HeaderOutput_raw = []; 42 | HeaderOutput = []; 43 | local_hdr_bypass_till = 0; local_coz_pkt_bypass_bgn = 0; local_coz_pkt_bypass_end = 0; 44 | for headeridx=1:size(SegCoarseTime,1) 45 | thisHdrCoarseTime = SegCoarseTime(headeridx,:); 46 | thisHdrCoarseFreqHz = SegCoarseFreqHz(headeridx); 47 | if dbg_test_hdr_decoding_flag == 0 48 | herepassinfo = []; 49 | else 50 | herepassinfo = idealFoundDataSeg{1}.headerinfo{headeridx}; 51 | end 52 | 53 | if LRF_cfg.for_link_test_flag 54 | if thisHdrCoarseTime(1) < local_hdr_bypass_till 55 | continue; 56 | end 57 | if thisHdrCoarseTime(1) > local_coz_pkt_bypass_bgn && thisHdrCoarseTime(1) < local_coz_pkt_bypass_end 58 | continue; 59 | end 60 | end 61 | 62 | HeaderOutput_raw{headeridx} = lrfh_fine_est_header(LRFHSS_time_sig,thisHdrCoarseTime,thisHdrCoarseFreqHz, herepassinfo, LRF_cfg); 63 | if HeaderOutput_raw{headeridx}.shouldkeepflag == 0 64 | fprintf(1,'*** sync peak check FAILED *** candi header %d, coarse time %d, coarse freq %.2f\n', headeridx, thisHdrCoarseTime(1), thisHdrCoarseFreqHz); 65 | continue; 66 | end 67 | [HeaderOutput_raw{headeridx}.bits, HeaderOutput_raw{headeridx}.GMSKdemodvaladj] = lrfh_demodulate_header(LRFHSS_time_sig,HeaderOutput_raw{headeridx},LRF_cfg); 68 | 69 | HeaderOutput_raw{headeridx}.deint = lrfh_deinterleaving_hdr(HeaderOutput_raw{headeridx}.bits); 70 | [a,b] = lrfh_decode_hdr(HeaderOutput_raw{headeridx}.deint, LRF_cfg.myTrellis_header); 71 | HeaderOutput_raw{headeridx}.decode = a; 72 | HeaderOutput_raw{headeridx}.info = b; 73 | if LRF_cfg.region == LRF_cfg.region_val_US 74 | hereshouldaddflag = HeaderOutput_raw{headeridx}.info.CRCpass && HeaderOutput_raw{headeridx}.info.payloadlen <= LRF_cfg.max_payload_byte_num ... 75 | && HeaderOutput_raw{headeridx}.info.grid == 0 && HeaderOutput_raw{headeridx}.info.hop == 1 ... 76 | && isequal(HeaderOutput_raw{headeridx}.info.BW, [1 0 0 0]) ... 77 | && bi2de(HeaderOutput_raw{headeridx}.info.hopseq(end:-1:1)) < 384 ... 78 | && (HeaderOutput_raw{headeridx}.info.CR == 1 || HeaderOutput_raw{headeridx}.info.CR == 3); 79 | elseif LRF_cfg.region == LRF_cfg.region_val_EU 80 | hereshouldaddflag = HeaderOutput_raw{headeridx}.info.CRCpass && HeaderOutput_raw{headeridx}.info.payloadlen <= LRF_cfg.max_payload_byte_num ... 81 | && HeaderOutput_raw{headeridx}.info.grid == 1 && HeaderOutput_raw{headeridx}.info.hop == 1 ... 82 | && isequal(HeaderOutput_raw{headeridx}.info.BW, [0 0 1 0]) ... 83 | && bi2de(HeaderOutput_raw{headeridx}.info.hopseq(end:-1:1)) < 384 ... 84 | && (HeaderOutput_raw{headeridx}.info.CR == 1 || HeaderOutput_raw{headeridx}.info.CR == 3); 85 | end 86 | if hereshouldaddflag 87 | thishdr = HeaderOutput_raw{headeridx}; 88 | if thishdr.info.CR == 1 89 | thishdr.header_count = 2; 90 | elseif thishdr.info.CR == 3 91 | thishdr.header_count = 3; 92 | else 93 | thishdr.header_count = 3; % NOTE: our trace will not lead to here 94 | end 95 | thishdr.thishdridx = thishdr.header_count - thishdr.info.syncindex; 96 | thishdr.payload_length_de = thishdr.info.payloadlen; 97 | thishdr.payload_length_bits = 8*(thishdr.payload_length_de+2)+6; 98 | coff = [6/5 3/2 2 3 ]; 99 | thishdr.data_in_bitcount = ceil(thishdr.payload_length_bits*coff(thishdr.info.CR+1)); 100 | if LRF_cfg.for_link_test_flag 101 | local_coz_pkt_bypass_bgn = thishdr.start + LRF_cfg.staysmplnum_hdr*(thishdr.header_count-thishdr.thishdridx+1) - LRF_cfg.lookdist - 1; % NOTE: may need some work 102 | local_coz_pkt_bypass_end = local_coz_pkt_bypass_bgn + LRF_cfg.smblsmplnum*thishdr.data_in_bitcount - 10000; 103 | local_hdr_bypass_till = thishdr.start + LRF_cfg.staysmplnum_hdr - 10000; 104 | end 105 | HeaderOutput{length(HeaderOutput)+1} = thishdr; 106 | fprintf(1,'decoding candi header %d, found header %d: idx %d, CR %d, start %d, freq %.2f, power %.2f, cost %.2f\n', headeridx, length(HeaderOutput), ... 107 | thishdr.info.syncindex, thishdr.info.CR, thishdr.start, thishdr.foundfreq, thishdr.maxsyncval, thishdr.info.min_cost) 108 | else 109 | fprintf(1,'decoding candi header %d, FAIL, coarse time %d, coarse freq %.2f\n', headeridx, thisHdrCoarseTime(1), thisHdrCoarseFreqHz); 110 | end 111 | end 112 | 113 | FoundDataSeg = []; 114 | for headeridx=1:length(HeaderOutput) 115 | thishdr = HeaderOutput{headeridx}; 116 | thishdridx = thishdr.thishdridx; 117 | if thishdridx <= 0 118 | continue; 119 | end 120 | freq_mulconst = 0.9537; %(HeaderOutput{2}.foundfreq - HeaderOutput{1}.foundfreq)/(freq_header(2)-freq_header(1)) 121 | 122 | thispkt = []; 123 | thispkt.CRCpass = 0; 124 | thispkt.CR = thishdr.info.CR; 125 | thispkt.payload_length_de = thishdr.payload_length_de; 126 | thispkt.payload_length_bits = thishdr.payload_length_bits; 127 | thispkt.data_in_bitcount = thishdr.data_in_bitcount; 128 | thispkt.num_frags = ceil(thispkt.data_in_bitcount/48); 129 | thispkt.num_bits_frags = zeros(1,thispkt.num_frags); 130 | thispkt.num_bits_frags(1:end) = 48; 131 | thispkt.num_bits_frags(end) = thispkt.data_in_bitcount - 48*(thispkt.num_frags-1); 132 | thispkt.grid = thishdr.info.grid; 133 | thispkt.enable_hop = thishdr.info.hop; 134 | thispkt.BW = thishdr.info.BW; 135 | thispkt.hop_seq_id = thishdr.info.hopseq; 136 | thispkt.header_count = thishdr.header_count; 137 | thispkt.freq_all = calculate_freq_from_hop_seq_id(thispkt.grid, thispkt.enable_hop, thispkt.header_count, thispkt.BW, thispkt.hop_seq_id, thispkt.num_frags); 138 | thispkt.freq_header = thispkt.freq_all(1:thispkt.header_count); 139 | thispkt.freq = thispkt.freq_all(thispkt.header_count+1:end); 140 | thispkt.freq_addvalhere = thishdr.foundfreq - thispkt.freq_header(thishdridx)*freq_mulconst; 141 | thispkt.freq_Hz = thispkt.freq * freq_mulconst + thispkt.freq_addvalhere; 142 | if max(abs(thispkt.freq_Hz)) > LRF_cfg.allBW * 0.8 % NOTE: used to be 0.6 143 | continue; 144 | % fprintf(1,'pkt %d: CRC FAIL\n', pktidx); 145 | end 146 | thispkt.freq_header_Hz = thispkt.freq_header * freq_mulconst + thispkt.freq_addvalhere; 147 | thispkt.start = thishdr.start + LRF_cfg.staysmplnum_hdr*(thispkt.header_count-thishdridx+1) - LRF_cfg.lookdist - 1; % NOTE: may need some work 148 | if thispkt.start - LRF_cfg.staysmplnum_hdr*thispkt.header_count + LRF_cfg.lookdist + 1 <= 0 149 | continue; 150 | end 151 | if thispkt.start + LRF_cfg.staysmplnum_data*thispkt.num_frags > size(LRFHSS_time_sig,2) 152 | continue; 153 | end 154 | thispkt.foundwithhdr = thishdridx; 155 | thispkt.headerinfo = cell(1,thispkt.header_count); 156 | thispkt.headerinfo{thishdridx} = HeaderOutput{headeridx}; 157 | thispkt.SegCoarseTime = []; 158 | for fragmentidx=1:thispkt.num_frags 159 | thispkt.SegCoarseTime(fragmentidx,1) = thispkt.start + LRF_cfg.staysmplnum_data*(fragmentidx-1); 160 | if fragmentidx < thispkt.num_frags 161 | thispkt.SegCoarseTime(fragmentidx,2) = thispkt.SegCoarseTime(fragmentidx,1) + LRF_cfg.staysmplnum_data-1; 162 | else 163 | thispkt.SegCoarseTime(fragmentidx,2) = thispkt.SegCoarseTime(fragmentidx,1) + (thispkt.num_bits_frags(end)+1)*LRF_cfg.smblsmplnum; 164 | end 165 | end 166 | thispkt.hdrpower = zeros(1,thispkt.header_count); 167 | thispkt.hdrpower(thishdridx) = thishdr.maxsyncval; 168 | 169 | shouldaddflag = 1; 170 | for pidx=1:length(FoundDataSeg) 171 | if lrfh_is_same_detected_pkt(thispkt, FoundDataSeg{pidx}) 172 | if max(FoundDataSeg{pidx}.hdrpower) < thispkt.hdrpower(thishdridx) 173 | for h=1:thispkt.header_count 174 | if ~isempty(FoundDataSeg{pidx}.headerinfo{h}) && h ~= thishdridx 175 | thispkt.headerinfo{h} = FoundDataSeg{pidx}.headerinfo{h}; 176 | thispkt.hdrpower(h) = FoundDataSeg{pidx}.hdrpower(h); 177 | end 178 | end 179 | FoundDataSeg{pidx} = thispkt; 180 | end 181 | if FoundDataSeg{pidx}.hdrpower(thishdridx) < thispkt.hdrpower(thishdridx) 182 | FoundDataSeg{pidx}.headerinfo{thishdridx} = thispkt.headerinfo{thishdridx}; 183 | FoundDataSeg{pidx}.hdrpower(thishdridx) = thispkt.hdrpower(thishdridx); 184 | end 185 | shouldaddflag = 0; 186 | break; 187 | end 188 | end 189 | if lrfh_sim_decode_try_count > 1 190 | % NOTE: run decoding only twice, so just check the last one 191 | for lastpktidx=1:length(lrfh_sim_result{lrfh_sim_decode_try_count-1}) 192 | thatpkt = lrfh_sim_result{lrfh_sim_decode_try_count-1}{lastpktidx}; 193 | if lrfh_is_same_detected_pkt(thispkt, thatpkt) && thatpkt.CRCpass 194 | shouldaddflag = 0; 195 | break; 196 | end 197 | end 198 | end 199 | if shouldaddflag 200 | tempp = length(FoundDataSeg); 201 | FoundDataSeg{tempp+1} = thispkt; 202 | end 203 | end 204 | 205 | for pktidx=1:length(FoundDataSeg) 206 | thispkt = FoundDataSeg{pktidx}; 207 | maxsyncval_samples = []; 208 | for h=1:thispkt.header_count 209 | if ~isempty(thispkt.headerinfo{h}) 210 | maxsyncval_samples = [maxsyncval_samples, thispkt.headerinfo{h}.maxsyncval]; 211 | end 212 | end 213 | FoundDataSeg{pktidx}.estpower = mean(maxsyncval_samples); 214 | end -------------------------------------------------------------------------------- /LR-FHSS/peakfinder.m: -------------------------------------------------------------------------------- 1 | % Copyright (c) 2016, Nathanael C. Yoder 2 | % All rights reserved. 3 | % 4 | % Redistribution and use in source and binary forms, with or without 5 | % modification, are permitted provided that the following conditions are 6 | % met: 7 | % 8 | % * Redistributions of source code must retain the above copyright 9 | % notice, this list of conditions and the following disclaimer. 10 | % * Redistributions in binary form must reproduce the above copyright 11 | % notice, this list of conditions and the following disclaimer in 12 | % the documentation and/or other materials provided with the distribution 13 | % 14 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 18 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | % POSSIBILITY OF SUCH DAMAGE. 25 | 26 | 27 | function varargout = peakfinder(x0, sel, thresh, extrema, includeEndpoints, interpolate) 28 | %PEAKFINDER Noise tolerant fast peak finding algorithm 29 | % INPUTS: 30 | % x0 - A real vector from the maxima will be found (required) 31 | % sel - The amount above surrounding data for a peak to be, 32 | % identified (default = (max(x0)-min(x0))/4). Larger values mean 33 | % the algorithm is more selective in finding peaks. 34 | % thresh - A threshold value which peaks must be larger than to be 35 | % maxima or smaller than to be minima. 36 | % extrema - 1 if maxima are desired, -1 if minima are desired 37 | % (default = maxima, 1) 38 | % includeEndpoints - If true the endpoints will be included as 39 | % possible extrema otherwise they will not be included 40 | % (default = true) 41 | % interpolate - If true quadratic interpolation will be performed 42 | % around each extrema to estimate the magnitude and the 43 | % position of the peak in terms of fractional indicies. Note that 44 | % unlike the rest of this function interpolation assumes the 45 | % input is equally spaced. To recover the x_values of the input 46 | % rather than the fractional indicies you can do: 47 | % peakX = x0 + (peakLoc - 1) * dx 48 | % where x0 is the first x value and dx is the spacing of the 49 | % vector. Output peakMag to recover interpolated magnitudes. 50 | % See example 2 for more information. 51 | % (default = false) 52 | % 53 | % OUTPUTS: 54 | % peakLoc - The indicies of the identified peaks in x0 55 | % peakMag - The magnitude of the identified peaks 56 | % 57 | % [peakLoc] = peakfinder(x0) returns the indicies of local maxima that 58 | % are at least 1/4 the range of the data above surrounding data. 59 | % 60 | % [peakLoc] = peakfinder(x0,sel) returns the indicies of local maxima 61 | % that are at least sel above surrounding data. 62 | % 63 | % [peakLoc] = peakfinder(x0,sel,thresh) returns the indicies of local 64 | % maxima that are at least sel above surrounding data and larger 65 | % (smaller) than thresh if you are finding maxima (minima). 66 | % 67 | % [peakLoc] = peakfinder(x0,sel,thresh,extrema) returns the maxima of the 68 | % data if extrema > 0 and the minima of the data if extrema < 0 69 | % 70 | % [peakLoc] = peakfinder(x0,sel,thresh,extrema, includeEndpoints) 71 | % returns the endpoints as possible extrema if includeEndpoints is 72 | % considered true in a boolean sense 73 | % 74 | % [peakLoc, peakMag] = peakfinder(x0,sel,thresh,extrema,interpolate) 75 | % returns the results of results of quadratic interpolate around each 76 | % extrema if interpolate is considered to be true in a boolean sense 77 | % 78 | % [peakLoc, peakMag] = peakfinder(x0,...) returns the indicies of the 79 | % local maxima as well as the magnitudes of those maxima 80 | % 81 | % If called with no output the identified maxima will be plotted along 82 | % with the input data. 83 | % 84 | % Note: If repeated values are found the first is identified as the peak 85 | % 86 | % Example 1: 87 | % t = 0:.0001:10; 88 | % x = 12*sin(10*2*pi*t)-3*sin(.1*2*pi*t)+randn(1,numel(t)); 89 | % x(1250:1255) = max(x); 90 | % peakfinder(x) 91 | % 92 | % Example 2: 93 | % ds = 100; % Downsample factor 94 | % dt = .001; % Time step 95 | % ds_dt = ds*dt; % Time delta after downsampling 96 | % t0 = 1; 97 | % t = t0:dt:5 + t0; 98 | % x = 0.2-sin(0.01*2*pi*t)+3*cos(7/13*2*pi*t+.1)-2*cos((1+pi/10)*2*pi*t+0.2)-0.2*t; 99 | % x(end) = min(x); 100 | % x_ds = x(1:ds:end); % Downsample to test interpolation 101 | % [minLoc, minMag] = peakfinder(x_ds, .8, 0, -1, false, true); 102 | % minT = t0 + (minLoc - 1) * ds_dt; % Take into account 1 based indexing 103 | % p = plot(t,x,'-',t(1:ds:end),x_ds,'o',minT,minMag,'rv'); 104 | % set(p(2:end), 'linewidth', 2); % Show the markers more clearly 105 | % legend('Actual Data', 'Input Data', 'Estimated Peaks'); 106 | % Copyright Nathanael C. Yoder 2015 (nyoder@gmail.com) 107 | 108 | % Perform error checking and set defaults if not passed in 109 | narginchk(1, 6); 110 | nargoutchk(0, 2); 111 | 112 | s = size(x0); 113 | flipData = s(1) < s(2); 114 | len0 = numel(x0); 115 | if len0 ~= s(1) && len0 ~= s(2) 116 | error('PEAKFINDER:Input','The input data must be a vector') 117 | elseif isempty(x0) 118 | varargout = {[],[]}; 119 | return; 120 | end 121 | if ~isreal(x0) 122 | warning('PEAKFINDER:NotReal','Absolute value of data will be used') 123 | x0 = abs(x0); 124 | end 125 | 126 | if nargin < 2 || isempty(sel) 127 | sel = (max(x0)-min(x0))/4; 128 | elseif ~isnumeric(sel) || ~isreal(sel) 129 | sel = (max(x0)-min(x0))/4; 130 | warning('PEAKFINDER:InvalidSel',... 131 | 'The selectivity must be a real scalar. A selectivity of %.4g will be used',sel) 132 | elseif numel(sel) > 1 133 | warning('PEAKFINDER:InvalidSel',... 134 | 'The selectivity must be a scalar. The first selectivity value in the vector will be used.') 135 | sel = sel(1); 136 | end 137 | 138 | if nargin < 3 || isempty(thresh) 139 | thresh = []; 140 | elseif ~isnumeric(thresh) || ~isreal(thresh) 141 | thresh = []; 142 | warning('PEAKFINDER:InvalidThreshold',... 143 | 'The threshold must be a real scalar. No threshold will be used.') 144 | elseif numel(thresh) > 1 145 | thresh = thresh(1); 146 | warning('PEAKFINDER:InvalidThreshold',... 147 | 'The threshold must be a scalar. The first threshold value in the vector will be used.') 148 | end 149 | 150 | if nargin < 4 || isempty(extrema) 151 | extrema = 1; 152 | else 153 | extrema = sign(extrema(1)); % Should only be 1 or -1 but make sure 154 | if extrema == 0 155 | error('PEAKFINDER:ZeroMaxima','Either 1 (for maxima) or -1 (for minima) must be input for extrema'); 156 | end 157 | end 158 | 159 | if nargin < 5 || isempty(includeEndpoints) 160 | includeEndpoints = true; 161 | end 162 | 163 | if nargin < 6 || isempty(interpolate) 164 | interpolate = false; 165 | end 166 | 167 | x0 = extrema*x0(:); % Make it so we are finding maxima regardless 168 | thresh = thresh*extrema; % Adjust threshold according to extrema. 169 | dx0 = diff(x0); % Find derivative 170 | dx0(dx0 == 0) = -eps; % This is so we find the first of repeated values 171 | ind = find(dx0(1:end-1).*dx0(2:end) < 0)+1; % Find where the derivative changes sign 172 | 173 | % Include endpoints in potential peaks and valleys as desired 174 | if includeEndpoints 175 | x = [x0(1);x0(ind);x0(end)]; 176 | ind = [1;ind;len0]; 177 | minMag = min(x); 178 | leftMin = minMag; 179 | else 180 | x = x0(ind); 181 | minMag = min(x); 182 | leftMin = min(x(1), x0(1)); 183 | end 184 | 185 | % x only has the peaks, valleys, and possibly endpoints 186 | len = numel(x); 187 | 188 | if len > 2 % Function with peaks and valleys 189 | % Set initial parameters for loop 190 | tempMag = minMag; 191 | foundPeak = false; 192 | 193 | if includeEndpoints 194 | % Deal with first point a little differently since tacked it on 195 | % Calculate the sign of the derivative since we tacked the first 196 | % point on it does not neccessarily alternate like the rest. 197 | signDx = sign(diff(x(1:3))); 198 | if signDx(1) <= 0 % The first point is larger or equal to the second 199 | if signDx(1) == signDx(2) % Want alternating signs 200 | x(2) = []; 201 | ind(2) = []; 202 | len = len-1; 203 | end 204 | else % First point is smaller than the second 205 | if signDx(1) == signDx(2) % Want alternating signs 206 | x(1) = []; 207 | ind(1) = []; 208 | len = len-1; 209 | end 210 | end 211 | end 212 | 213 | % Skip the first point if it is smaller so we always start on a 214 | % maxima 215 | if x(1) >= x(2) 216 | ii = 0; 217 | else 218 | ii = 1; 219 | end 220 | 221 | % Preallocate max number of maxima 222 | maxPeaks = ceil(len/2); 223 | peakLoc = zeros(maxPeaks,1); 224 | peakMag = zeros(maxPeaks,1); 225 | cInd = 1; 226 | % Loop through extrema which should be peaks and then valleys 227 | while ii < len 228 | ii = ii+1; % This is a peak 229 | % Reset peak finding if we had a peak and the next peak is bigger 230 | % than the last or the left min was small enough to reset. 231 | if foundPeak 232 | tempMag = minMag; 233 | foundPeak = false; 234 | end 235 | 236 | % Found new peak that was lager than temp mag and selectivity larger 237 | % than the minimum to its left. 238 | if x(ii) > tempMag && x(ii) > leftMin + sel 239 | tempLoc = ii; 240 | tempMag = x(ii); 241 | end 242 | 243 | % Make sure we don't iterate past the length of our vector 244 | if ii == len 245 | break; % We assign the last point differently out of the loop 246 | end 247 | 248 | ii = ii+1; % Move onto the valley 249 | % Come down at least sel from peak 250 | if ~foundPeak && tempMag > sel + x(ii) 251 | foundPeak = true; % We have found a peak 252 | leftMin = x(ii); 253 | peakLoc(cInd) = tempLoc; % Add peak to index 254 | peakMag(cInd) = tempMag; 255 | cInd = cInd+1; 256 | elseif x(ii) < leftMin % New left minima 257 | leftMin = x(ii); 258 | end 259 | end 260 | 261 | % Check end point 262 | if includeEndpoints 263 | if x(end) > tempMag && x(end) > leftMin + sel 264 | peakLoc(cInd) = len; 265 | peakMag(cInd) = x(end); 266 | cInd = cInd + 1; 267 | elseif ~foundPeak && tempMag > minMag % Check if we still need to add the last point 268 | peakLoc(cInd) = tempLoc; 269 | peakMag(cInd) = tempMag; 270 | cInd = cInd + 1; 271 | end 272 | elseif ~foundPeak 273 | if x(end) > tempMag && x(end) > leftMin + sel 274 | peakLoc(cInd) = len; 275 | peakMag(cInd) = x(end); 276 | cInd = cInd + 1; 277 | elseif tempMag > min(x0(end), x(end)) + sel 278 | peakLoc(cInd) = tempLoc; 279 | peakMag(cInd) = tempMag; 280 | cInd = cInd + 1; 281 | end 282 | end 283 | 284 | % Create output 285 | if cInd > 1 286 | peakInds = ind(peakLoc(1:cInd-1)); 287 | peakMags = peakMag(1:cInd-1); 288 | else 289 | peakInds = []; 290 | peakMags = []; 291 | end 292 | else % This is a monotone function where an endpoint is the only peak 293 | [peakMags,xInd] = max(x); 294 | if includeEndpoints && peakMags > minMag + sel 295 | peakInds = ind(xInd); 296 | else 297 | peakMags = []; 298 | peakInds = []; 299 | end 300 | end 301 | 302 | % Apply threshold value. Since always finding maxima it will always be 303 | % larger than the thresh. 304 | if ~isempty(thresh) 305 | m = peakMags>thresh; 306 | peakInds = peakInds(m); 307 | peakMags = peakMags(m); 308 | end 309 | 310 | if interpolate && ~isempty(peakMags) 311 | middleMask = (peakInds > 1) & (peakInds < len0); 312 | noEnds = peakInds(middleMask); 313 | 314 | magDiff = x0(noEnds + 1) - x0(noEnds - 1); 315 | magSum = x0(noEnds - 1) + x0(noEnds + 1) - 2 * x0(noEnds); 316 | magRatio = magDiff ./ magSum; 317 | 318 | peakInds(middleMask) = peakInds(middleMask) - magRatio/2; 319 | peakMags(middleMask) = peakMags(middleMask) - magRatio .* magDiff/8; 320 | end 321 | 322 | % Rotate data if needed 323 | if flipData 324 | peakMags = peakMags.'; 325 | peakInds = peakInds.'; 326 | end 327 | 328 | % Change sign of data if was finding minima 329 | if extrema < 0 330 | peakMags = -peakMags; 331 | x0 = -x0; 332 | end 333 | 334 | % Plot if no output desired 335 | if nargout == 0 336 | if isempty(peakInds) 337 | disp('No significant peaks found') 338 | else 339 | figure; 340 | plot(1:len0,x0,'.-',peakInds,peakMags,'ro','linewidth',2); 341 | end 342 | else 343 | varargout = {peakInds,peakMags}; 344 | end --------------------------------------------------------------------------------