From 1b502599482444ed2bac1b0eff5c9c29fccfac7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Sat, 21 Feb 2015 10:23:02 +0100 Subject: [PATCH] add USB IR toy --- README.md | 20 +++++ usb ir toy/decode.rb | 92 +++++++++++++++++++ usb ir toy/ir.rb | 209 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 README.md create mode 100755 usb ir toy/decode.rb create mode 100755 usb ir toy/ir.rb diff --git a/README.md b/README.md new file mode 100644 index 0000000..2877c74 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +LaserTag infra-red greande. +based on the MilesTag v2 protocol + +usb ir toy +========== + +script to record and decode infra-red signal using the Dangerous Prototypes USB IR toy board. +I had to replace the 38 kHz demodulator with a 56 kHz demodulator. + +ir.rb +----- + +script to use the USB IR toy. +can detect frequency, record transmission, play recordings, ... + +decode.rb +--------- + +decode the MilesTag v2 recorded transmission + diff --git a/usb ir toy/decode.rb b/usb ir toy/decode.rb new file mode 100755 index 0000000..8c4dd1f --- /dev/null +++ b/usb ir toy/decode.rb @@ -0,0 +1,92 @@ +#!/usr/bin/env ruby +# encoding: UTF-8 +# tested with ruby 1.9.1 and 1.9.3 +# decode milestag II message recorded by ir.rb + +UNIT = 21.3333 # ir burst unit, in µs + +# verify command parameters +raise "provide a IR record file to parse" if ARGV.size!=1 +# read file +file = File.open(ARGV[0],"r") + +# parse ticks in IR packets +ir_packets = [] +packet = [] +while data = file.read(2) do + # one IR packet + burst = data.unpack('n')[0] + if burst==0xffff then + # create next packet + packet << 1 # trailing space + ir_packets << packet + packet = [] + else + # decode tick + burst *= UNIT + # the number of ticks (a tick = 600µs) + tick = (burst/600.0).round + packet << tick + end +end +puts "got #{ir_packets.size} IR packets" +puts ir_packets*" " + +# decode ticks in bits +bit_packets = [] +ir_packets.each do |ir_packet| + bit_packet = [] + header = false + space = false + ir_packet.each do |ir| + # wait for header + if ir==4 then + if bit_packet.size>0 then + bit_packets << bit_packet + bit_packet = [] + end + header = true + space = true + else + next unless header + if space then + space = false + next + else + if ir==1 or ir==2 then + bit_packet<<(ir-1) + space = true + else + puts "broken burst" + header = false + end + end + end + end + bit_packets << bit_packet +end +puts "got #{bit_packets.size} bit packets" +puts bit_packets*" " + +# parse bit packet +bit_packets.each do |bit_packet| + if bit_packet[0]==0 then + # shot packet + if bit_packet.size!=14 then + puts "broken shot packet" + else + player = (bit_packet[1,7]*"").to_i(2) + team = (bit_packet[8,2]*"").to_i(2) + damage = (bit_packet[10,4]*"").to_i(2) + puts "shot: player=#{player}, team=#{team}, damage=#{damage}" + end + else + # control packet + if bit_packet.size%8!=0 then + puts "broken control packet" + else + data = (bit_packet*"").to_i(2).to_s(16).rjust(bit_packet.size/4,"0") + puts "control packet: 0x#{data}" + end + end +end diff --git a/usb ir toy/ir.rb b/usb ir toy/ir.rb new file mode 100755 index 0000000..83e9019 --- /dev/null +++ b/usb ir toy/ir.rb @@ -0,0 +1,209 @@ +#!/usr/bin/env ruby +# encoding: UTF-8 +# tested with ruby 1.9.1 and 1.9.3 +# USB IR Toy tool +# based on http://dangerousprototypes.com/docs/USB_IR_Toy:_Sampling_mode +require 'serialport' + +# display debug messages +@debug = false + +def help + puts "USB IR Toy tool" + puts "" + puts "option:" + puts "\t--help,-h\t\tdisplay this help" + puts "\t--device,-d \tuse this serial port" + puts "\t--record,-r \trecord IR stream into this file" + puts "\t--play,-p \tplay IR activity from this file" + puts "\t--freq,-f\t\tdetect frequency of IR signal" + puts "\t--laser,-l\t\tuse lasertag frequency (56kHz) when transmitting" + puts "\t--traffic,-t\t\tshow communication traffic with USB IR toy" + exit 0 +end + +device = "/dev/ttyACM0" +mode = :record +file = nil +fast = false # false = default 38.4kHz, true = 56kHz +while argument = ARGV.shift do + case argument + when "--help","-h" + help + when "--device","-d" + raise "no device specified" unless device = ARGV.shift + when "--record","-r" + raise "no file specified" unless file = ARGV.shift + file = File.open(file,"w") + mode = :record + when "--play","-p" + raise "no file specified" unless file = ARGV.shift + file = File.open(file,"r") + mode = :play + when "--freq","-f" + mode = :freq + when "--laser","-l" + fast = true + when "--traffic","-t" + @debug = true + else + help + end +end + +# read n bytes from serial +def read(bytes) + data = @serial.read bytes + str = "> " + data.bytes {|b| str += "%02x " % b} + puts str if @debug + return data +end + +# write data to serial +def write(data) + to_send = nil + to_send = case data + when Fixnum + [data].pack "C" + when Array + data.pack "C*" + when String + data + else + raise "unknown data format to send" + end + + str = "< " + to_send.bytes {|b| str += "%02x " % b} + puts str if @debug + + @serial.write to_send +end + +# open serial to USB IR Toy (adapt device path. any baudrate is accepted) +print "opening serial port #{device} … " +$stdout.flush +@serial = SerialPort.new(device,115200) +@serial.read_timeout = 0 +puts "✓" + +print "reseting … " +$stdout.flush +# empty buffer +@serial.read_timeout = 500 +loop while @serial.read(1) +@serial.read_timeout = 0 +5.times do + write [0x00] +end +puts "✓" +sleep 0.5 + +print "getting version … " +$stdout.flush +write 'v' +@serial.read_timeout = 500 +version = read 4 +@serial.read_timeout = 0 +if version and version.length==4 then + puts "HW v#{version[1,1]}, FW v#{version[2,2]}" +else + puts "USB IR Toy is stuck. un- and re-plug it" + exit 0 +end + +print "enter sampling mode … " +$stdout.flush +write 's' +protocol = nil +begin + protocol = read 3 +end until protocol and protocol.length==3 +puts "protocol version: #{protocol}" + +case mode +when :record + STATES = ["–","_"] + state = 0 + unit = 21.3333 #us + loop do + data = read 2 + time = data.unpack('n')[0] + if time==0xffff then + puts "|" + else + print "#{STATES[state%2]}#{(time*unit/100).round}#{STATES[state%2]}" + end + $stdout.flush + state += 1 + file.write data if file + end +when :play + if fast then + puts "setting transmit modulation to 56kHz used by lasertag" + # use command http://dangerousprototypes.com/docs/USB_IR_Toy:_Sampling_mode#Setup_transmit_modulation_.280x06.29 + # use value from http://www.micro-examples.com/public/microex-navig/doc/097-pwm-calculator (55555.56kHz) + write [0x06,0x35,0x00] + end + print "enter transmit mode (with handshake) … " + $stdout.flush + write 0x24 # enable by count report + write 0x25 # notify on complete + write 0x26 # handshake + puts "✓" + buffer = 0 # free buffer size + burst = true # start new burst transmittion + while data = file.read(2) do + if burst then + burst = false + buffer = 0 + write 0x03 # transmit + puts "transmitting burst" + end + # wait for free buffer + if buffer<=0 then + buffer=read(1).unpack("C")[0] + puts "new buffer size: #{buffer}" + end + write data + buffer -= data.size + if data.unpack('n')[0]==0xffff then + # restart transmit + burst = true + # last handshake(s) until count byte + begin + report = read(1) + sleep 1 + end until report and report[0]=='t' + # byte count + count = read(2) + puts "#{count[0,2].unpack('n')[0]} bytes processed" + # notification + case tmp=read(1) + when "C" + puts "transmission complete" + when "F" + puts "buffer underrun" + else + puts "unknown notification: #{tmp}" + end + sleep 1 + end + end +when :freq + print "waiting for a burst … " + loop until read(2).unpack('n')[0]==0xffff + $stdout.flush + puts "✓" + write 0x04 + data = read(8).unpack('n*') + # PIC clock is 12MHz + clock = 1.0/12E6 + freq1 = (1/(clock*(data[0])))/(1E3) + freq2 = (1/(clock*(data[1]-data[0])))/(1E3) + freq3 = (1/(clock*(data[2]-data[1])))/(1E3) + puts "detected frequency: #{freq1.round(2)}kHz, #{freq2.round(2)}kHz, #{freq3.round(2)}kHz" +else + puts "unknown mode #{mode}" +end