Class: PWN::SDR::Decoder::LoRa::PayloadIQ

Inherits:
Object
  • Object
show all
Defined in:
lib/pwn/sdr/decoder/lora.rb

Overview

Raw PHY receive path. Coding equations adapted from MIT LoRaPHY, commit 4fddd9a7b47682781c663608bfc4f196e4bf656d (Xu et al., ACM TOSN 2022, doi:10.1145/3546869). Full attribution/license: documentation/LoRaPHY-LICENSE.txt. Independent gr-lora_sdr vectors: spec/fixtures/sdr/lora/README.md. SF7-12 with explicit or configured implicit headers, optional CRC, LDRO and inverted IQ; no fractional CFO or clock-drift tracking. Keep acquisition and packet state together across arbitrary IQ chunks.

Instance Method Summary collapse

Constructor Details

#initialize(rate:, bw:, sf:, sync_word: 0x12, implicit_header: false, payload_length: nil, cr: 1, crc: true, ldro: nil, invert_iq: false) ⇒ PayloadIQ

rubocop:disable Metrics/ClassLength

Raises:



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/pwn/sdr/decoder/lora.rb', line 154

def initialize(rate:, bw:, sf:, sync_word: 0x12, implicit_header: false, payload_length: nil, cr: 1, crc: true, ldro: nil, invert_iq: false) # rubocop:disable Metrics/ParameterLists
  raise ArgumentError, 'LoRa requires SF7-12, BW125/250/500k and finite sample_rate >= BW' unless sf.is_a?(Integer) && SF_RANGE.cover?(sf) && [125_000, 250_000, 500_000].include?(bw) && rate.is_a?(Numeric) && rate.finite? && rate >= bw
  raise ArgumentError, 'Implicit LoRa requires payload_length 0..255 and cr 1..4' if implicit_header && (!payload_length.is_a?(Integer) || !(0..255).cover?(payload_length) || !(1..4).cover?(cr))

  @implicit = implicit_header
  @length = payload_length
  @cr = cr
  @crc = crc
  @ldro = ldro.nil? ? (1 << sf).fdiv(bw) > 0.016 : ldro
  @invert = invert_iq

  @rate = rate
  @bw = bw
  @sf = sf
  raise ArgumentError, 'LoRa sync_word must be an integer byte' unless sync_word.is_a?(Integer) && (0..255).cover?(sync_word)

  @sync = sync_word
  @n = 1 << sf
  @buf = []
  @scan = 8 * @n
  @reference = Array.new(@n) do |k|
    phase = Math::PI * k * ((k.to_f / @n) - 1)
    Complex(Math.cos(phase), Math.sin(phase))
  end
end

Instance Method Details

#feed_iq(samples, rate: nil, &block) ⇒ Object

Raises:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/pwn/sdr/decoder/lora.rb', line 180

def feed_iq(samples, rate: nil, &block)
  raise ArgumentError, 'LoRa sample rate changed' if rate && rate != @rate

  @buf.concat(resample_input(samples))
  loop do
    if @packet
      break unless receive_packet(&block)
    else
      break if @scan + (3 * @n) > @buf.length / 2

      acquire
      @scan += @n / 2 unless @packet
      # Bounded acquisition history, preserving six upchirps and sync.
      if !@packet && @scan > 24 * @n
        removed = @scan - (8 * @n)
        @buf.shift(removed * 2)
        @scan -= removed
      end
    end
  end
end