live_f1-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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