commit 8b67b811d1b176b7b4e992438a24d92ee69f0222 Author: King Kévin Date: Sat Jul 12 18:15:21 2014 -0700 add script to decode rtl_fm recordings diff --git a/sdr/decode.rb b/sdr/decode.rb new file mode 100755 index 0000000..09723f7 --- /dev/null +++ b/sdr/decode.rb @@ -0,0 +1,144 @@ +#!/usr/bin/env ruby +# encoding: utf-8 +# ruby: 2.1 +=begin +this script will open a AM raw audio file gerenate by rtl_fm and decode the megacode message from it +=end + +# constants +RATE = 24000 # the output sample rate, in Hz +# the expected samples are little endian signed 16 bits intergers +THRESHOLD = ((2**16)/2)*0.5 +TOLERANCE = 1.10 # how much deviation to accept + +raise "provide raw AM file to decode as argument" unless ARGV[0] and File.exist? ARGV[0] and File.file? ARGV[0] + +raw = IO.binread ARGV[0] # read raw file +samples = raw.unpack "s<*" # get samples (little endian signed 16 bits intergers) + +# detect falling edges, after crossing the threshold +on = false # has the threshold been crossed +edges = [] +samples.each_index do |i| + sample = samples[i] + if !on then # detect when threshold is crossed + if sample > THRESHOLD then + on = true + edges << {:sample => i, :rising => true} + end + else + if sample < THRESHOLD then + on = false + edges << {:sample => i, :rising => false} + end + end +end + +edges.collect!{|edge| {:ms => edge[:sample]/(RATE/1000.0), :rising => edge[:rising]}} # convert edges to milliseconds +# the bursts (HF activity) should last 1ms +# verify if this is true, and ignore oscilastion within this 1ms +pulses = [] # one transmission has 24 pulses +pulse_begin = nil # first rising edge +pulse_end = nil # last falling edge +edges.each do |edge| + raise "nil edge" unless edge + # search first pulse (rising edge) + unless pulse_begin then + next unless edge[:rising] + pulse_begin = edge + end + # detect pulses: falling and rising edge within 1ms + # ignore edges within this 1ms + if !edge[:rising] then + pulse_end ||= edge + if pulse_end[:ms]-pulse_begin[:ms]<=1*TOLERANCE then + pulse_end = edge + else # this is too long for a pulse. discard it + pulse_begin = nil + end + else # rising edge + if edge[:ms]-pulse_begin[:ms]>1*TOLERANCE then # this is the beginning of the next pulse + raise "two rising egdes without falling edge detected" unless pulse_end # this should not happen + pulses << pulse_begin + pulse_begin = edge + pulse_end = nil + end # ignore rising egdes within a pulse + end +end +# add last pulse +pulses << pulse_begin if pulse_begin and pulse_end + +# split pulses into groups +# one group has 24 pulses with a bitframe of 6ms +# a blank bitframe without pulse separates groups +# we will split groups when no pulse occured within after 2 bitframes +groups = [] +previous_pulse = nil +group = [] +pulses.each do |pulse| + if previous_pulse then + group << previous_pulse + if (pulse[:ms]-previous_pulse[:ms])>=2*6*(1-(TOLERANCE-1)) then + groups << group + group = [] + end + end + previous_pulse = pulse +end +# add last pulse +group << pulses[-1] +groups << group + +# transmissions have 24 pulses +transmissions = groups.select {|group| group.size==24} + +# verify that there is exactly 24 times one pulse per 6ms bitframe +# the pulse is either after 2 ms or 5 ms +values = [] +transmissions.each_index do |transmission_i| + transmission = transmissions[transmission_i] + # use the previous pulse to sync + sync = transmission[0][:ms]-3 # the first pulse is always in the second halt (after 5 ms) + bits = [] + transmission.each_index do |pulse_i| + pulse = transmission[pulse_i] + # the next pulse is after 6 or 9 ms + offset = pulse[:ms]-sync +# puts "transmission #{transmission_i}, pulse #{pulse_i}, offset: #{offset} ms" + if offset>-1.5 and offset<=1.5 then + bits << 0 + sync = pulse[:ms]+6 + elsif offset>3-1.5 and offset<=3+1.5 then + bits << 1 + sync = pulse[:ms]-3+6 + else + puts "could not decode bit on transmission #{transmission_i} pulse #{pulse_i}" + break + end + end + # if there are 24 bit, decode + if bits.size==24 then + value = 0 + bits.each do |bit| + value = (value << 1) + bit + end + values << value + else +# puts "found #{bits.size} bits" + end +end + +# print results +puts "# egdes: #{edges.size}" +puts "# pulses: #{pulses.size}" +puts "# groups: #{groups.size} (#{groups.collect{|group| group.size}*', '})" +puts "# transmissions: #{transmissions.size}" +puts "# values: #{values.size}" +unless values.empty? then + puts "values: " + values.each do |value| + data_bits = (value & 0x07) + system_code = ((value & 0x7fffff) >> 3) + puts "- value: #{value} (0x#{value.to_s(16).rjust(6,'0')}), system code: #{system_code} (0x#{system_code.to_s(16)}), databits: #{data_bits} (0x#{data_bits.to_s(16)})" + end +end