live_f1-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/.autotest +23 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +65 -0
  6. data/Guardfile +11 -0
  7. data/README.rdoc +62 -0
  8. data/Rakefile +11 -0
  9. data/bin/live_f1_example +74 -0
  10. data/features/fixtures/sessions/2012.03.china.qualifying/7136/keyframes.yaml +32722 -0
  11. data/features/fixtures/sessions/2012.03.china.qualifying/7136/session.key +1 -0
  12. data/features/fixtures/sessions/2012.03.china.qualifying/session.bin +0 -0
  13. data/features/fixtures/sessions/2012.04.bahrain.practice.2/7164/keyframes.yaml +22403 -0
  14. data/features/fixtures/sessions/2012.04.bahrain.practice.2/7164/session.key +1 -0
  15. data/features/fixtures/sessions/2012.04.bahrain.practice.2/session.bin +0 -0
  16. data/features/fixtures/sessions/2012.05.bahrain.race.post/7167/keyframes.yaml +1204 -0
  17. data/features/fixtures/sessions/2012.05.bahrain.race.post/7167/session.key +1 -0
  18. data/features/fixtures/sessions/2012.05.bahrain.race.post/session.bin +0 -0
  19. data/features/live_f1.feature +14 -0
  20. data/features/step_definitions/live_f1_steps.rb +69 -0
  21. data/features/support/env.rb +9 -0
  22. data/lib/live_f1/debug.rb +6 -0
  23. data/lib/live_f1/enum.rb +9 -0
  24. data/lib/live_f1/event.rb +23 -0
  25. data/lib/live_f1/packet/car/best_lap_time.rb +10 -0
  26. data/lib/live_f1/packet/car/driver.rb +14 -0
  27. data/lib/live_f1/packet/car/gap.rb +10 -0
  28. data/lib/live_f1/packet/car/interval.rb +10 -0
  29. data/lib/live_f1/packet/car/lap_count.rb +10 -0
  30. data/lib/live_f1/packet/car/lap_time.rb +10 -0
  31. data/lib/live_f1/packet/car/num_pits.rb +10 -0
  32. data/lib/live_f1/packet/car/number.rb +14 -0
  33. data/lib/live_f1/packet/car/period_1.rb +11 -0
  34. data/lib/live_f1/packet/car/period_2.rb +11 -0
  35. data/lib/live_f1/packet/car/period_3.rb +11 -0
  36. data/lib/live_f1/packet/car/pit_count.rb +18 -0
  37. data/lib/live_f1/packet/car/pit_lap_1.rb +10 -0
  38. data/lib/live_f1/packet/car/pit_lap_2.rb +10 -0
  39. data/lib/live_f1/packet/car/pit_lap_3.rb +10 -0
  40. data/lib/live_f1/packet/car/position.rb +14 -0
  41. data/lib/live_f1/packet/car/position_history.rb +14 -0
  42. data/lib/live_f1/packet/car/position_update.rb +13 -0
  43. data/lib/live_f1/packet/car/sector_1.rb +11 -0
  44. data/lib/live_f1/packet/car/sector_2.rb +11 -0
  45. data/lib/live_f1/packet/car/sector_3.rb +11 -0
  46. data/lib/live_f1/packet/car.rb +13 -0
  47. data/lib/live_f1/packet/decryptable.rb +22 -0
  48. data/lib/live_f1/packet/header.rb +127 -0
  49. data/lib/live_f1/packet/sector_time.rb +28 -0
  50. data/lib/live_f1/packet/sys/commentary.rb +33 -0
  51. data/lib/live_f1/packet/sys/copyright.rb +9 -0
  52. data/lib/live_f1/packet/sys/key_frame.rb +17 -0
  53. data/lib/live_f1/packet/sys/notice.rb +10 -0
  54. data/lib/live_f1/packet/sys/reset.rb +9 -0
  55. data/lib/live_f1/packet/sys/session_start.rb +25 -0
  56. data/lib/live_f1/packet/sys/speed.rb +29 -0
  57. data/lib/live_f1/packet/sys/timestamp.rb +23 -0
  58. data/lib/live_f1/packet/sys/track_status.rb +18 -0
  59. data/lib/live_f1/packet/sys/weather.rb +33 -0
  60. data/lib/live_f1/packet/sys.rb +10 -0
  61. data/lib/live_f1/packet.rb +129 -0
  62. data/lib/live_f1/source/keyframe.rb +30 -0
  63. data/lib/live_f1/source/live.rb +163 -0
  64. data/lib/live_f1/source/log.rb +29 -0
  65. data/lib/live_f1/source/session.rb +41 -0
  66. data/lib/live_f1/source.rb +83 -0
  67. data/lib/live_f1/version.rb +3 -0
  68. data/lib/live_f1.rb +32 -0
  69. data/live_f1-core.gemspec +26 -0
  70. data/spec/live_f1/event_spec.rb +5 -0
  71. data/spec/live_f1/packet/car/best_lap_time_spec.rb +19 -0
  72. data/spec/live_f1/packet/car/driver_spec.rb +20 -0
  73. data/spec/live_f1/packet/car/gap_spec.rb +20 -0
  74. data/spec/live_f1/packet/car/interval_spec.rb +20 -0
  75. data/spec/live_f1/packet/car/lap_count_spec.rb +20 -0
  76. data/spec/live_f1/packet/car/lap_time_spec.rb +20 -0
  77. data/spec/live_f1/packet/car/number_spec.rb +20 -0
  78. data/spec/live_f1/packet/car/period_1_spec.rb +21 -0
  79. data/spec/live_f1/packet/car/period_2_spec.rb +21 -0
  80. data/spec/live_f1/packet/car/period_3_spec.rb +21 -0
  81. data/spec/live_f1/packet/car/pit_count_spec.rb +20 -0
  82. data/spec/live_f1/packet/car/pit_lap_1_spec.rb +20 -0
  83. data/spec/live_f1/packet/car/pit_lap_2_spec.rb +20 -0
  84. data/spec/live_f1/packet/car/pit_lap_3_spec.rb +20 -0
  85. data/spec/live_f1/packet/car/position_history_spec.rb +20 -0
  86. data/spec/live_f1/packet/car/position_spec.rb +20 -0
  87. data/spec/live_f1/packet/car/position_update_spec.rb +19 -0
  88. data/spec/live_f1/packet/car/sector_1_spec.rb +21 -0
  89. data/spec/live_f1/packet/car/sector_2_spec.rb +21 -0
  90. data/spec/live_f1/packet/car/sector_3_spec.rb +21 -0
  91. data/spec/live_f1/packet/header_spec.rb +148 -0
  92. data/spec/live_f1/packet/sys/commentary_spec.rb +45 -0
  93. data/spec/live_f1/packet/sys/copyright_spec.rb +19 -0
  94. data/spec/live_f1/packet/sys/key_frame_spec.rb +39 -0
  95. data/spec/live_f1/packet/sys/notice_spec.rb +20 -0
  96. data/spec/live_f1/packet/sys/reset_spec.rb +26 -0
  97. data/spec/live_f1/packet/sys/session_start_spec.rb +31 -0
  98. data/spec/live_f1/packet/sys/speed_spec.rb +20 -0
  99. data/spec/live_f1/packet/sys/timestamp_spec.rb +34 -0
  100. data/spec/live_f1/packet/sys/track_status_spec.rb +20 -0
  101. data/spec/live_f1/packet/sys/weather_spec.rb +20 -0
  102. data/spec/live_f1/packet_spec.rb +112 -0
  103. data/spec/live_f1/source/keyframe_spec.rb +56 -0
  104. data/spec/live_f1/source/live_spec.rb +106 -0
  105. data/spec/live_f1/source/session_spec.rb +39 -0
  106. data/spec/live_f1/source_spec.rb +140 -0
  107. data/spec/spec_helper.rb +10 -0
  108. data/spec/support/packet_type_examples.rb +122 -0
  109. metadata +337 -0
@@ -0,0 +1,10 @@
1
+ module LiveF1
2
+ class Packet
3
+ class Sys
4
+ class Notice < Sys
5
+ include Packet::Type::Long
6
+ include Packet::Decryptable
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module LiveF1
2
+ class Packet
3
+ class Sys
4
+ class Reset < Sys
5
+ include Packet::Type::Special
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module LiveF1
2
+ class Packet
3
+ class Sys
4
+ # A SessionStart packet is the first packet emitted for a session.
5
+ class SessionStart < Sys
6
+ include Packet::Type::Short
7
+
8
+ # The formula1.com unique identifier for this session
9
+ def session_number
10
+ data[1..-1].to_i
11
+ end
12
+
13
+ # The type of event this is (practise, qualifying or race) as defined
14
+ # by the constants in Event::Type
15
+ def event_type
16
+ header.data & 0b0111
17
+ end
18
+
19
+ def inspect
20
+ "Event #{session_number} Start (#{Event::Type.name_for event_type})"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module LiveF1
2
+ class Packet
3
+ class Sys
4
+ class Speed < Sys
5
+ include Packet::Type::Long
6
+ include Packet::Decryptable
7
+
8
+ def trap
9
+ data.bytes.first
10
+ end
11
+
12
+ def speeds
13
+ data[1..-1].split(/\s+/).each_slice(2).map { |d,s| "%s: %d" % [d,s] }.join(", ")
14
+ end
15
+
16
+ def to_s
17
+ out = case trap
18
+ when 1..4
19
+ speeds
20
+ else
21
+ data[1..-1]
22
+ end
23
+
24
+ "[%d] %s" % [trap, out]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module LiveF1
2
+ class Packet
3
+ class Sys
4
+ # Individual packets don't include any timestamp data to indicate when
5
+ # they happened. However, at selected (but irregular) intervals Timestamp
6
+ # packets are emitted containing the number of seconds since the start of
7
+ # the session.
8
+ class Timestamp < Sys
9
+ include Packet::Type::Timestamp
10
+ include Packet::Decryptable
11
+
12
+ # The number of seconds since the start of the session.
13
+ def number
14
+ data.unpack("v").first
15
+ end
16
+
17
+ def to_s
18
+ number
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module LiveF1
2
+ class Packet
3
+ class Sys
4
+ class TrackStatus < Sys
5
+ include Packet::Type::Short
6
+ include Packet::Decryptable
7
+
8
+ def status
9
+ data.to_i
10
+ end
11
+
12
+ def to_s
13
+ Event::TrackStatus.name_for(status)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ require 'live_f1/enum'
2
+
3
+ module LiveF1
4
+ class Packet
5
+ class Sys
6
+ class Weather < Sys
7
+ module Metric
8
+ extend LiveF1::Enum
9
+ SESSION_CLOCK = 0
10
+ TRACK_TEMPERATURE = 1
11
+ AIR_TEMPERATURE = 2
12
+ WET_TRACK = 3
13
+ WIND_SPEED = 4
14
+ HUMIDITY = 5
15
+ AIR_PRESSURE = 6
16
+ WIND_DIRECTION = 7
17
+ end
18
+ include Metric
19
+
20
+ include Packet::Type::Short
21
+ include Packet::Decryptable
22
+
23
+ def to_s
24
+ "%-18s - %s" % [Metric.name_for(metric), data]
25
+ end
26
+
27
+ def metric
28
+ header.data & 0b0111
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ module LiveF1
2
+ class Packet
3
+ class Sys < Packet
4
+ end
5
+ end
6
+ end
7
+
8
+ Dir[File.join(File.dirname(__FILE__),'sys','*')].each do |file|
9
+ require file
10
+ end
@@ -0,0 +1,129 @@
1
+ require 'live_f1/debug'
2
+
3
+ module LiveF1
4
+ # A Packet represents a raw instruction sent from the live timing server to
5
+ # the live timing applet.
6
+ #
7
+ # It is represented in the data stream by a variable-length series of bytes,
8
+ # always starting with a 2-byte "header" and then a number of other bytes
9
+ # depending on the specific data being represented.
10
+ class Packet
11
+ # There are 4 broad categories of packet, where the only difference is how
12
+ # we work out how many bytes of data are expected from the stream.
13
+ #
14
+ # As described in the Header documentation, the 2 header bytes are split
15
+ # into information about the packet type and corresponding car, along with
16
+ # up to 7 bits of payload data.
17
+ module Type
18
+ module Short
19
+ # Short packets use 4 bits of the header data to represent the packet
20
+ # length. Normally this would mean a maximum length of 15, except if a
21
+ # short packet says it has a length of 15 it actually has a length of 0
22
+ def length
23
+ l = (header.data >> 3)
24
+ l == 0x0f ? 0 : l
25
+ end
26
+
27
+ def spare_bits
28
+ 3
29
+ end
30
+ end
31
+
32
+ module Long
33
+ # Long packets use all 7 bits of the header data to represent the packet
34
+ # length. This allows for a maximum packet length of 127 bytes.
35
+ def length
36
+ header.data
37
+ end
38
+
39
+ def spare_bits
40
+ 0
41
+ end
42
+ end
43
+
44
+ module Special
45
+ # Special packets never have any additional data.
46
+ def length
47
+ 0
48
+ end
49
+
50
+ def spare_bits
51
+ 7
52
+ end
53
+ end
54
+
55
+ module Timestamp
56
+ # Timestamp packets always contain 2 bytes of data.
57
+ def length
58
+ 2
59
+ end
60
+
61
+ def spare_bits
62
+ 0
63
+ end
64
+ end
65
+ end
66
+
67
+ attr_reader :source
68
+ attr_reader :header
69
+ attr_accessor :data
70
+
71
+ alias bytes data
72
+
73
+ # First extracts a Header from the given source and then extracts the
74
+ # packet it represents, based on the given event type.
75
+ def self.from_source source, event_type
76
+ header = Header.from_source(source, event_type)
77
+ packet = header.packet_klass.new source, header
78
+ data = source.read_bytes(packet.length)
79
+ packet.data = data
80
+ packet
81
+ end
82
+
83
+ def initialize source, header
84
+ @source = source
85
+ @header = header
86
+ end
87
+
88
+ def to_s
89
+ data.inspect
90
+ end
91
+
92
+ def spare_bits
93
+ 0
94
+ end
95
+
96
+ # Returns the bits of the header data which aren't used to determine the
97
+ # packet length
98
+ def spare_data
99
+ "%0#{spare_bits}b" % (header.data & (2 ** spare_bits - 1))
100
+ end
101
+
102
+ def inspect
103
+ if LiveF1.debug
104
+ "[%7s] %-23s %s" % [spare_data, leader, to_s]
105
+ else
106
+ "%-23s %s" % [leader, to_s]
107
+ end
108
+ end
109
+
110
+ def leader
111
+ self.class.name.sub(/LiveF1::Packet::/, '')
112
+ end
113
+
114
+ private
115
+ # Since some classes override +data=+ to deal with encrypted data, here's a
116
+ # method that can be used in rare cases (e.g. testing) where we need to
117
+ # bypass that process
118
+ def set_data new_data
119
+ @data = new_data
120
+ end
121
+ end
122
+ end
123
+
124
+
125
+ require_relative 'packet/header'
126
+ require_relative 'packet/decryptable'
127
+ require_relative 'packet/sector_time'
128
+ require_relative 'packet/sys'
129
+ require_relative 'packet/car'
@@ -0,0 +1,30 @@
1
+ require 'open-uri'
2
+
3
+ module LiveF1
4
+ class Source
5
+ class Keyframe < Source
6
+ attr_reader :io, :parent
7
+
8
+ def initialize io, parent
9
+ @io = io
10
+ @parent = parent
11
+ end
12
+
13
+ def read_bytes num
14
+ io.read(num) or raise EOFError
15
+ end
16
+
17
+ def session
18
+ parent.session
19
+ end
20
+
21
+ def session= new_session
22
+ parent.session = new_session
23
+ end
24
+
25
+ def decryption_key session_number
26
+ parent.decryption_key(session_number)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,163 @@
1
+ require 'net/http'
2
+ require 'cgi'
3
+ require 'timeout'
4
+ require 'fileutils'
5
+ require 'yaml'
6
+ require_relative 'keyframe'
7
+
8
+ module LiveF1
9
+ class Source
10
+ class Live < Source
11
+
12
+ HOST = "80.231.178.249"
13
+ PORT = 4321
14
+
15
+ attr_reader :username, :password
16
+
17
+ def initialize username, password
18
+ @username = username
19
+ @password = password
20
+ end
21
+
22
+ def read_bytes num
23
+ begin
24
+ Timeout.timeout(0.5) do
25
+ socket.read(num) or raise EOFError
26
+ end
27
+ rescue Timeout::Error, Errno::ETIMEDOUT => e
28
+ log.flush
29
+ socket.write("\n")
30
+ socket.flush
31
+ retry
32
+ end
33
+ end
34
+
35
+ def keyframe number = nil
36
+ io = open("http://#{HOST}/#{keyframe_filename(number)}")
37
+ log.keyframe(number, io.read) if number
38
+ io.rewind
39
+ Source::Keyframe.new io, self
40
+ rescue SocketError
41
+ raise ConnectionError, "Unable to connect to live timing server #{HOST}"
42
+ end
43
+
44
+ def decryption_key session_number
45
+ key = open("http://#{HOST}/reg/getkey/#{session_number}.asp?auth=#{auth}").read.to_i(16)
46
+ raise ConnectionError, "Unable to access session key for session #{session_number}. This could indicate incorrect credentials or an issue with the formula1.com key server" if key.zero?
47
+ key
48
+ end
49
+
50
+ # Sets the base directory for live data log files to be stored
51
+ def log_dir= log_directory
52
+ Log.dir = log_directory
53
+ end
54
+
55
+ def run
56
+ super do |packet|
57
+ case packet
58
+ when LiveF1::Packet::Sys::SessionStart
59
+ log.start packet.session_number
60
+ log.key session.decryption_key
61
+ when LiveF1::Packet::Sys::KeyFrame
62
+ keyframe(packet.number)
63
+ end
64
+
65
+ log.packet packet
66
+
67
+ yield packet
68
+ end
69
+ rescue Errno::ECONNRESET
70
+ log.flush
71
+ retry
72
+ rescue Exception
73
+ log.flush
74
+ raise
75
+ end
76
+
77
+ def session= new_session
78
+ super
79
+ end
80
+
81
+ def log
82
+ LogProxy
83
+ end
84
+
85
+ private
86
+ def socket
87
+ @socket ||= TCPSocket.open(HOST, PORT)
88
+ end
89
+
90
+ def auth
91
+ response = Net::HTTP.post_form URI.parse("http://#{HOST}/reg/login"), {"email" => username, "password" => password}
92
+ CGI::Cookie.parse(response["Set-Cookie"])["USER"].first
93
+ end
94
+
95
+ def keyframe_filename number
96
+ "keyframe#{ "_%05d" % number if number}.bin"
97
+ end
98
+
99
+ class LogProxy
100
+ class << self
101
+ def start session_number
102
+ flush
103
+ @log = Log.new session_number
104
+ end
105
+
106
+ [:key, :packet, :keyframe].each do |m|
107
+ define_method m do |*args|
108
+ @log.send(m, *args) if @log
109
+ end
110
+ end
111
+
112
+ def flush
113
+ @log.flush if @log
114
+ @log = nil
115
+ end
116
+ end
117
+ end
118
+
119
+ class Log
120
+ class << self
121
+ attr_reader :dir
122
+
123
+ def dir= log_directory
124
+ @dir = Pathname.new(log_directory).join(Date.today.strftime("%Y%m%d"))
125
+ FileUtils.mkdir_p(@dir)
126
+ end
127
+
128
+ def active?
129
+ !!@dir
130
+ end
131
+ end
132
+
133
+ def initialize session_number
134
+ @filename = Log.dir.join("%s.%s.f1" % [session_number, Time.now.strftime("%H%M%S")]) if Log.active?
135
+ @data = {
136
+ key: nil,
137
+ bytes: "",
138
+ keyframes: {},
139
+ }
140
+ end
141
+
142
+ def key k
143
+ @data[:key] = k
144
+ end
145
+
146
+ def packet p
147
+ @data[:bytes] << p.header.bytes << p.bytes
148
+ end
149
+
150
+ def keyframe n, k
151
+ @data[:keyframes][n] = k
152
+ end
153
+
154
+ def flush
155
+ File.open(@filename, "w") { |f| f.write YAML.dump(@data) } if @filename
156
+ end
157
+ end
158
+
159
+ class ConnectionError < RuntimeError
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'keyframe'
2
+
3
+ module LiveF1
4
+ class Source
5
+ class Log < Source
6
+
7
+ attr_reader :file, :data
8
+
9
+ def initialize file
10
+ @file = file
11
+ @data = YAML.load(@file.read)
12
+ @bytes = StringIO.new(@data[:bytes])
13
+ end
14
+
15
+ def read_bytes num
16
+ @bytes.read(num)
17
+ end
18
+
19
+ def keyframe number = nil
20
+ keyframe_data = data[:keyframes][number.to_s] || ""
21
+ Source::Keyframe.new StringIO.new(keyframe_data), self
22
+ end
23
+
24
+ def decryption_key session_number
25
+ data[:key].to_i
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ module LiveF1
2
+ class Source
3
+ # A source's Session object holds information about the current state of
4
+ # the data stream, and can use that to decrypt the encrypted packets which
5
+ # are retrieved this way.
6
+ #
7
+ # = Decryption
8
+ #
9
+ # Decrypting a string of bytes from the timing stream relies on knowing two
10
+ # pieces of information, a decryption key which is specific to the session
11
+ # in progress, and a decryption salt which starts at a known value but is
12
+ # mutated with every byte that is decrypted.
13
+ #
14
+ # The decryption key can only be obtained from the live timing servers with
15
+ # a valid formula1.com Live Timing account, as described in
16
+ # LiveF1::Source::Live
17
+ class Session < Struct.new(:number, :event_type, :decryption_key)
18
+ INITIAL_DECRYPTION_SALT = 0x55555555
19
+
20
+ attr_accessor :decryption_salt
21
+
22
+ def initialize number, event_type, decryption_key
23
+ super
24
+ reset_decryption_salt!
25
+ end
26
+
27
+ # Decrypts the given string using this session's decryption_key and the
28
+ # current state of the decryption_salt.
29
+ def decrypt input
30
+ input.bytes.map do |b|
31
+ self.decryption_salt = (decryption_salt >> 1) ^ ((decryption_salt & 0x01).zero? ? 0 : decryption_key)
32
+ b ^ (decryption_salt & 0xff)
33
+ end.pack("c*")
34
+ end
35
+
36
+ def reset_decryption_salt!
37
+ self.decryption_salt = INITIAL_DECRYPTION_SALT
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,83 @@
1
+ require_relative 'source/live'
2
+ require_relative 'source/log'
3
+ require_relative 'source/session'
4
+ require_relative 'packet'
5
+
6
+ module LiveF1
7
+ # A Source reads raw live timing data packets from the live timing stream and
8
+ # processes them into semantic packets representing discrete instructions
9
+ # sent from the live timing server.
10
+ #
11
+ # These instructions relate to a low-level manipulation of the screen which
12
+ # would be displayed by the live timing Java applet. For example, when a
13
+ # car's sector 1 time is received, the applet needs to clear the other two
14
+ # sector times from the previous lap:
15
+ #
16
+ # Driver Lap S1 S2 S3
17
+ # L. HAMILTON 1:35.324 38.4 22.7 34.2
18
+ #
19
+ # becomes
20
+ #
21
+ # Driver Lap S1 S2 S3
22
+ # L. HAMILTON 1:35.324 39.1
23
+ #
24
+ # However, the live timing stream achieves this by sending three "packets" of
25
+ # data: a sector 1 packet containing "39.1", and sector 2 and 3 packets both
26
+ # containing the empty string "".
27
+ #
28
+ # The methods on a Source instance output all of these packets, even the ones
29
+ # which are useless from a data point of view.
30
+ class Source
31
+ attr_accessor :session
32
+
33
+ # Starts processing data from the relevant location, and yields all packets
34
+ # which are generated from the data stream.
35
+ def run
36
+ # We load packets from a couple of places below, but however we get them
37
+ # we treat them the same, hence this proc.
38
+ packet_processor = proc do |packet|
39
+ case packet
40
+ when LiveF1::Packet::Sys::SessionStart
41
+ self.session = Source::Session.new(packet.session_number, packet.event_type, decryption_key(packet.session_number))
42
+ when LiveF1::Packet::Sys::KeyFrame
43
+ session.reset_decryption_salt!
44
+ end
45
+ yield packet
46
+ end
47
+
48
+ # The live timing stream starts by loading the most recent Keyframe and
49
+ # reading its data. The keyframe contains "setup" information like driver
50
+ # names and numbers to get the app ready to receive live packets.
51
+ #
52
+ # Importantly we check to see whether the source *knows* about other
53
+ # keyframes. This is because the keyframe is itself another source (the
54
+ # +.run+ we call here is actually this very method on a different
55
+ # subclass)
56
+ if self.respond_to? :keyframe
57
+ keyframe.run(&packet_processor)
58
+ end
59
+
60
+ # Now that any keyframe has been parsed, we start streaming raw data
61
+ while packet = read_packet
62
+ packet_processor.call(packet)
63
+ end
64
+ end
65
+
66
+ def decrypt bytes
67
+ session.decrypt bytes
68
+ end
69
+
70
+ def read_packet
71
+ Packet.from_source(self, (session.event_type if session))
72
+ rescue EOFError
73
+ end
74
+
75
+ def read_bytes num
76
+ raise NotImplementedError, "#read_bytes should be implemented by #{self.class}"
77
+ end
78
+
79
+ def decryption_key session_number
80
+ raise NotImplementedError, "#decryption_key should be implemented by #{self.class}"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,3 @@
1
+ module LiveF1
2
+ VERSION = '0.0.1'
3
+ end
data/lib/live_f1.rb ADDED
@@ -0,0 +1,32 @@
1
+ require_relative 'live_f1/source'
2
+ require_relative 'live_f1/debug'
3
+
4
+ # =Formula 1 live timing
5
+ #
6
+ # The LiveF1 library allows realtime parsing of data from the F1 live
7
+ # timing stream. It connects to the binary stream and turns it into a series of
8
+ # objects describing the stream
9
+ #
10
+ # ==Basics
11
+ #
12
+ # The live timing service is primarily used to control the live timing Java
13
+ # applet at http://www.formula1.com/live_timing. However, the richness
14
+ # of the data it provides means that the stream could be used to provide a much
15
+ # deeper view of a session than the applet itself provides. This library
16
+ # provides the very basic toolkit allowing such an application to be built using
17
+ # Ruby, but when using it it's important to remember the service was built
18
+ # around this one visual use.
19
+ #
20
+ # The stream generates packets from the start of every practice, qualifying and
21
+ # race session. However anyone connecting to the stream after the start of a
22
+ # session doesn't get sent the entire packet history. Instead, keyframes
23
+ # containing the current live timing state are regularly generated throughout a
24
+ # session, and new connections are given the latest keyframe followed by the
25
+ # packets generated since that keyframe.
26
+ #
27
+ # ==Usage
28
+ #
29
+ # See bin/live-f1 for usage examples
30
+ #
31
+ module LiveF1
32
+ end