Class: PWN::SDR::Decoder::RFID::EM4100IQ

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

Overview

EM4100 Manchester RF/64, RF/32 and RF/16 at a configured carrier. Frame layout and polarity: https://www.priority1design.com.au/em4100_protocol.html Only ASK Manchester is supported; not PSK, biphase, ISO14443 or EPC Gen2.

Instance Method Summary collapse

Constructor Details

#initialize(rate:, carrier_hz: 125_000, clocks_per_bit: 64, threshold: 0.5) ⇒ EM4100IQ

Returns a new instance of EM4100IQ.

Raises:

  • (ArgumentError)


94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/pwn/sdr/decoder/rfid.rb', line 94

def initialize(rate:, carrier_hz: 125_000, clocks_per_bit: 64, threshold: 0.5)
  raise ArgumentError, 'EM4100 clocks_per_bit must be 16, 32 or 64' unless [16, 32, 64].include?(clocks_per_bit)

  @rate = Float(rate)
  @half = @rate * clocks_per_bit / (2.0 * Float(carrier_hz))
  raise ArgumentError, 'EM4100 requires at least four samples per half bit' unless @half.finite? && @half >= 4

  @threshold = Float(threshold)**2
  @level = nil
  @remaining = 0
  @symbols = []
end

Instance Method Details

#feed_iq(samples, rate: nil) ⇒ Object

Raises:

  • (ArgumentError)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/pwn/sdr/decoder/rfid.rb', line 107

def feed_iq(samples, rate: nil)
  raise ArgumentError, 'sample rate changed midstream' if rate && (rate.to_f - @rate).abs.positive?
  raise ArgumentError, 'interleaved IQ pairs required' unless samples.length.even?

  samples.each_slice(2) do |i, q|
    level = ((i * i) + (q * q)) >= @threshold ? 1 : 0
    if level != @level
      @level = level
      @remaining = @half / 2.0
    end
    @remaining -= 1
    next if @remaining.positive?

    @remaining += @half
    @symbols << level
    @symbols.shift if @symbols.length > 128
    next unless @symbols.length == 128
    next unless @symbols.each_slice(2).all? { |a, b| a != b }

    bits = @symbols.each_slice(2).map(&:last)
    [bits, bits.map { |bit| bit ^ 1 }].each do |candidate|
      frame = RFID.parse_frame(bits: candidate)
      yield frame if frame && block_given?
    end
  end
end

#flushObject



134
135
136
# File 'lib/pwn/sdr/decoder/rfid.rb', line 134

def flush
  @symbols.clear # Never pad a truncated frame into a tag.
end