live_f1-core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +65 -0
- data/Guardfile +11 -0
- data/README.rdoc +62 -0
- data/Rakefile +11 -0
- data/bin/live_f1_example +74 -0
- data/features/fixtures/sessions/2012.03.china.qualifying/7136/keyframes.yaml +32722 -0
- data/features/fixtures/sessions/2012.03.china.qualifying/7136/session.key +1 -0
- data/features/fixtures/sessions/2012.03.china.qualifying/session.bin +0 -0
- data/features/fixtures/sessions/2012.04.bahrain.practice.2/7164/keyframes.yaml +22403 -0
- data/features/fixtures/sessions/2012.04.bahrain.practice.2/7164/session.key +1 -0
- data/features/fixtures/sessions/2012.04.bahrain.practice.2/session.bin +0 -0
- data/features/fixtures/sessions/2012.05.bahrain.race.post/7167/keyframes.yaml +1204 -0
- data/features/fixtures/sessions/2012.05.bahrain.race.post/7167/session.key +1 -0
- data/features/fixtures/sessions/2012.05.bahrain.race.post/session.bin +0 -0
- data/features/live_f1.feature +14 -0
- data/features/step_definitions/live_f1_steps.rb +69 -0
- data/features/support/env.rb +9 -0
- data/lib/live_f1/debug.rb +6 -0
- data/lib/live_f1/enum.rb +9 -0
- data/lib/live_f1/event.rb +23 -0
- data/lib/live_f1/packet/car/best_lap_time.rb +10 -0
- data/lib/live_f1/packet/car/driver.rb +14 -0
- data/lib/live_f1/packet/car/gap.rb +10 -0
- data/lib/live_f1/packet/car/interval.rb +10 -0
- data/lib/live_f1/packet/car/lap_count.rb +10 -0
- data/lib/live_f1/packet/car/lap_time.rb +10 -0
- data/lib/live_f1/packet/car/num_pits.rb +10 -0
- data/lib/live_f1/packet/car/number.rb +14 -0
- data/lib/live_f1/packet/car/period_1.rb +11 -0
- data/lib/live_f1/packet/car/period_2.rb +11 -0
- data/lib/live_f1/packet/car/period_3.rb +11 -0
- data/lib/live_f1/packet/car/pit_count.rb +18 -0
- data/lib/live_f1/packet/car/pit_lap_1.rb +10 -0
- data/lib/live_f1/packet/car/pit_lap_2.rb +10 -0
- data/lib/live_f1/packet/car/pit_lap_3.rb +10 -0
- data/lib/live_f1/packet/car/position.rb +14 -0
- data/lib/live_f1/packet/car/position_history.rb +14 -0
- data/lib/live_f1/packet/car/position_update.rb +13 -0
- data/lib/live_f1/packet/car/sector_1.rb +11 -0
- data/lib/live_f1/packet/car/sector_2.rb +11 -0
- data/lib/live_f1/packet/car/sector_3.rb +11 -0
- data/lib/live_f1/packet/car.rb +13 -0
- data/lib/live_f1/packet/decryptable.rb +22 -0
- data/lib/live_f1/packet/header.rb +127 -0
- data/lib/live_f1/packet/sector_time.rb +28 -0
- data/lib/live_f1/packet/sys/commentary.rb +33 -0
- data/lib/live_f1/packet/sys/copyright.rb +9 -0
- data/lib/live_f1/packet/sys/key_frame.rb +17 -0
- data/lib/live_f1/packet/sys/notice.rb +10 -0
- data/lib/live_f1/packet/sys/reset.rb +9 -0
- data/lib/live_f1/packet/sys/session_start.rb +25 -0
- data/lib/live_f1/packet/sys/speed.rb +29 -0
- data/lib/live_f1/packet/sys/timestamp.rb +23 -0
- data/lib/live_f1/packet/sys/track_status.rb +18 -0
- data/lib/live_f1/packet/sys/weather.rb +33 -0
- data/lib/live_f1/packet/sys.rb +10 -0
- data/lib/live_f1/packet.rb +129 -0
- data/lib/live_f1/source/keyframe.rb +30 -0
- data/lib/live_f1/source/live.rb +163 -0
- data/lib/live_f1/source/log.rb +29 -0
- data/lib/live_f1/source/session.rb +41 -0
- data/lib/live_f1/source.rb +83 -0
- data/lib/live_f1/version.rb +3 -0
- data/lib/live_f1.rb +32 -0
- data/live_f1-core.gemspec +26 -0
- data/spec/live_f1/event_spec.rb +5 -0
- data/spec/live_f1/packet/car/best_lap_time_spec.rb +19 -0
- data/spec/live_f1/packet/car/driver_spec.rb +20 -0
- data/spec/live_f1/packet/car/gap_spec.rb +20 -0
- data/spec/live_f1/packet/car/interval_spec.rb +20 -0
- data/spec/live_f1/packet/car/lap_count_spec.rb +20 -0
- data/spec/live_f1/packet/car/lap_time_spec.rb +20 -0
- data/spec/live_f1/packet/car/number_spec.rb +20 -0
- data/spec/live_f1/packet/car/period_1_spec.rb +21 -0
- data/spec/live_f1/packet/car/period_2_spec.rb +21 -0
- data/spec/live_f1/packet/car/period_3_spec.rb +21 -0
- data/spec/live_f1/packet/car/pit_count_spec.rb +20 -0
- data/spec/live_f1/packet/car/pit_lap_1_spec.rb +20 -0
- data/spec/live_f1/packet/car/pit_lap_2_spec.rb +20 -0
- data/spec/live_f1/packet/car/pit_lap_3_spec.rb +20 -0
- data/spec/live_f1/packet/car/position_history_spec.rb +20 -0
- data/spec/live_f1/packet/car/position_spec.rb +20 -0
- data/spec/live_f1/packet/car/position_update_spec.rb +19 -0
- data/spec/live_f1/packet/car/sector_1_spec.rb +21 -0
- data/spec/live_f1/packet/car/sector_2_spec.rb +21 -0
- data/spec/live_f1/packet/car/sector_3_spec.rb +21 -0
- data/spec/live_f1/packet/header_spec.rb +148 -0
- data/spec/live_f1/packet/sys/commentary_spec.rb +45 -0
- data/spec/live_f1/packet/sys/copyright_spec.rb +19 -0
- data/spec/live_f1/packet/sys/key_frame_spec.rb +39 -0
- data/spec/live_f1/packet/sys/notice_spec.rb +20 -0
- data/spec/live_f1/packet/sys/reset_spec.rb +26 -0
- data/spec/live_f1/packet/sys/session_start_spec.rb +31 -0
- data/spec/live_f1/packet/sys/speed_spec.rb +20 -0
- data/spec/live_f1/packet/sys/timestamp_spec.rb +34 -0
- data/spec/live_f1/packet/sys/track_status_spec.rb +20 -0
- data/spec/live_f1/packet/sys/weather_spec.rb +20 -0
- data/spec/live_f1/packet_spec.rb +112 -0
- data/spec/live_f1/source/keyframe_spec.rb +56 -0
- data/spec/live_f1/source/live_spec.rb +106 -0
- data/spec/live_f1/source/session_spec.rb +39 -0
- data/spec/live_f1/source_spec.rb +140 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/packet_type_examples.rb +122 -0
- metadata +337 -0
@@ -0,0 +1 @@
|
|
1
|
+
AF661706
|
Binary file
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Live timing connection
|
2
|
+
In order to follow a Formula 1 race live
|
3
|
+
As a user of the LiveF1 library
|
4
|
+
I want to receive packets of data
|
5
|
+
|
6
|
+
Scenario: Connecting before the session has started
|
7
|
+
Given the live timing session is about to start
|
8
|
+
When I successfully connect to the live timing service
|
9
|
+
Then I should receive packets of data
|
10
|
+
|
11
|
+
Scenario: Connecting after the session has completed
|
12
|
+
Given the live timing session has been completed
|
13
|
+
When I successfully connect to the live timing service
|
14
|
+
Then I should receive packets of data
|
@@ -0,0 +1,69 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require 'live_f1'
|
3
|
+
require 'cucumber/rspec/doubles'
|
4
|
+
|
5
|
+
Given /^the live timing session is about to start$/ do
|
6
|
+
# fixture_session '2012.04.bahrain.practice.2'
|
7
|
+
fixture_session '2012.03.china.qualifying'
|
8
|
+
end
|
9
|
+
|
10
|
+
Given /^the live timing session has been completed$/ do
|
11
|
+
fixture_session '2012.05.bahrain.race.post'
|
12
|
+
end
|
13
|
+
|
14
|
+
When /^I successfully connect to the live timing service$/ do
|
15
|
+
@stream = LiveF1::Source::Live.new 'gareth@example.com', 'swordfish'
|
16
|
+
end
|
17
|
+
|
18
|
+
Then /^I should receive packets of data$/ do
|
19
|
+
packets = []
|
20
|
+
@stream.run do |packet|
|
21
|
+
packets << packet
|
22
|
+
end
|
23
|
+
packets.should include_a(LiveF1::Packet::Sys::SessionStart)
|
24
|
+
end
|
25
|
+
|
26
|
+
def fixture_session name
|
27
|
+
fixture_base = File.expand_path(File.join(File.dirname(__FILE__),'../fixtures/sessions',name))
|
28
|
+
|
29
|
+
stream_filename = File.join(fixture_base,'session.bin')
|
30
|
+
# Stubbing a Socket with a File may have consequences
|
31
|
+
TCPSocket.stub(:open) { File.open(stream_filename) }
|
32
|
+
|
33
|
+
sessions = Dir[File.join(fixture_base,'*')].select { |f| File.directory? f }
|
34
|
+
sessions.each do |session|
|
35
|
+
keyframe_data = YAML.load(File.read(File.join(session, "keyframes.yaml")))
|
36
|
+
keyframe_data.each do |filename, data|
|
37
|
+
url = "http://80.231.178.249/#{filename}"
|
38
|
+
FakeWeb.register_uri(:get, url, :body => data)
|
39
|
+
end
|
40
|
+
|
41
|
+
session_number = File.basename(session)
|
42
|
+
keyfile = File.join(session,'session.key')
|
43
|
+
FakeWeb.register_uri(:post, 'http://80.231.178.249/reg/login', :set_cookie => "USER=abc123def")
|
44
|
+
FakeWeb.register_uri(:get, "http://80.231.178.249/reg/getkey/#{session_number}.asp?auth=abc123def", :body => keyfile)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# def live_timing_session &block
|
49
|
+
# receiver = Object.new
|
50
|
+
# class << receiver
|
51
|
+
# attr_writer :packets
|
52
|
+
#
|
53
|
+
# def packets
|
54
|
+
# @packets ||= []
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# def method_missing m, *args
|
58
|
+
# self.packets += MockPacket.send(m, *args)
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# yield receiver
|
63
|
+
#
|
64
|
+
# receiver.packets << nil
|
65
|
+
#
|
66
|
+
# socket = mock("socket")
|
67
|
+
# socket.stub(:read) { receiver.packets.shift }
|
68
|
+
# TCPSocket.stub(:open) { socket }
|
69
|
+
# end
|
data/lib/live_f1/enum.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module LiveF1
|
2
|
+
# Adds a convenience method for converting between constant values and names
|
3
|
+
module Enum
|
4
|
+
# Returns the first constant (in definition order) matching the given value
|
5
|
+
def name_for value
|
6
|
+
constants.detect { |c| const_get(c) == value }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'live_f1/enum'
|
2
|
+
|
3
|
+
module LiveF1
|
4
|
+
# An Event represents a usable metric relating to a car or live session.
|
5
|
+
class Event
|
6
|
+
module Type
|
7
|
+
extend LiveF1::Enum
|
8
|
+
RACE = 1
|
9
|
+
PRACTICE = 2
|
10
|
+
QUALIFYING = 3
|
11
|
+
end
|
12
|
+
include Type
|
13
|
+
|
14
|
+
module TrackStatus
|
15
|
+
extend LiveF1::Enum
|
16
|
+
GREEN_FLAG = 1
|
17
|
+
YELLOW_FLAG = 2
|
18
|
+
SAFETY_CAR_STANDBY = 3
|
19
|
+
SAFETY_CAR_DEPLOYED = 4
|
20
|
+
RED_FLAG = 5
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module LiveF1
|
2
|
+
class Packet
|
3
|
+
# Packets which mixin the Decryptable module represent data that is encrypted
|
4
|
+
# in the data stream.
|
5
|
+
#
|
6
|
+
# When setting the packet data we transparently decrypt the data, and also
|
7
|
+
# set a raw_data containing the original, encrypted bytes in case they are
|
8
|
+
# useful
|
9
|
+
module Decryptable
|
10
|
+
attr_reader :raw_data
|
11
|
+
|
12
|
+
def data= bytes
|
13
|
+
@raw_data = bytes
|
14
|
+
@data = source.decrypt(bytes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def bytes
|
18
|
+
@raw_data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require_relative '../packet'
|
2
|
+
require_relative '../event'
|
3
|
+
|
4
|
+
module LiveF1
|
5
|
+
class Packet
|
6
|
+
# The Unknown packet is a special placeholder packet for situations where a
|
7
|
+
# zero-length packet is delivered but seems to have no effect on the stream
|
8
|
+
class Unknown < Packet
|
9
|
+
include Packet::Type::Special
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"Unknown packet type #{header.packet_type}" + (header.car > 0 ? " for car #{header.car}" : "")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# A Header uses 2 bytes of data from a live timing stream to determine all
|
17
|
+
# the necessary information about the packet which follows it.
|
18
|
+
class Header < Struct.new(:data, :packet_type, :car, :event_type)
|
19
|
+
attr_reader :bytes
|
20
|
+
|
21
|
+
def self.from_source source, event_type
|
22
|
+
bytes = source.read_bytes(2)
|
23
|
+
raise "No data from #{source.inspect}" unless bytes.to_s.length == 2
|
24
|
+
bits = bytes.to_s.reverse.unpack("B*").first
|
25
|
+
_, data, packet_type, car = bits.match(/^(.{7})(.{4})(.{5})$/).to_a.map { |s| s.to_i(2) }
|
26
|
+
|
27
|
+
new(data, packet_type, car, event_type).tap do |header|
|
28
|
+
# TODO: Maybe need a nicer way of setting this
|
29
|
+
header.instance_variable_set "@bytes", bytes
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def car?
|
34
|
+
!car.zero?
|
35
|
+
end
|
36
|
+
|
37
|
+
def packet_klass
|
38
|
+
case
|
39
|
+
when car?
|
40
|
+
case event_type
|
41
|
+
when Event::RACE
|
42
|
+
case packet_type
|
43
|
+
when 0 then Packet::Car::PositionUpdate
|
44
|
+
when 1 then Packet::Car::Position
|
45
|
+
when 2 then Packet::Car::Number
|
46
|
+
when 3 then Packet::Car::Driver
|
47
|
+
when 4 then Packet::Car::Gap
|
48
|
+
when 5 then Packet::Car::Interval
|
49
|
+
when 6 then Packet::Car::LapTime
|
50
|
+
when 7 then Packet::Car::Sector1
|
51
|
+
when 8 then Packet::Car::PitLap1
|
52
|
+
when 9 then Packet::Car::Sector2
|
53
|
+
when 10 then Packet::Car::PitLap2
|
54
|
+
when 11 then Packet::Car::Sector3
|
55
|
+
when 12 then Packet::Car::PitLap3
|
56
|
+
when 13 then Packet::Car::NumPits
|
57
|
+
when 14 then nil
|
58
|
+
when 15 then Packet::Car::PositionHistory
|
59
|
+
end
|
60
|
+
when Event::PRACTICE
|
61
|
+
case packet_type
|
62
|
+
when 0 then Packet::Car::PositionUpdate
|
63
|
+
when 1 then Packet::Car::Position
|
64
|
+
when 2 then Packet::Car::Number
|
65
|
+
when 3 then Packet::Car::Driver
|
66
|
+
when 4 then Packet::Car::BestLapTime
|
67
|
+
when 5 then Packet::Car::Gap
|
68
|
+
when 6 then Packet::Car::Sector1
|
69
|
+
when 7 then Packet::Car::Sector2
|
70
|
+
when 8 then Packet::Car::Sector3
|
71
|
+
when 9 then Packet::Car::LapCount
|
72
|
+
when 10 then Packet::Car::LapCount
|
73
|
+
when 15 then nil
|
74
|
+
end
|
75
|
+
when Event::QUALIFYING
|
76
|
+
case packet_type
|
77
|
+
when 0 then Packet::Car::PositionUpdate
|
78
|
+
when 1 then Packet::Car::Position
|
79
|
+
when 2 then Packet::Car::Number
|
80
|
+
when 3 then Packet::Car::Driver
|
81
|
+
when 4 then Packet::Car::Period1
|
82
|
+
when 5 then Packet::Car::Period2
|
83
|
+
when 6 then Packet::Car::Period3
|
84
|
+
when 7 then Packet::Car::Sector1
|
85
|
+
when 8 then Packet::Car::Sector2
|
86
|
+
when 9 then Packet::Car::Sector3
|
87
|
+
when 10 then Packet::Car::LapCount
|
88
|
+
when 15 then nil
|
89
|
+
end
|
90
|
+
else
|
91
|
+
raise MissingEventType, "Unrecognised event type (#{event_type.inspect}), can't determine class for car packet #{packet_type.inspect}"
|
92
|
+
end
|
93
|
+
else
|
94
|
+
case packet_type
|
95
|
+
when 0 then Packet::Unknown
|
96
|
+
when 1 then Packet::Sys::SessionStart
|
97
|
+
when 2 then Packet::Sys::KeyFrame
|
98
|
+
when 3 then Packet::Unknown
|
99
|
+
when 4 then Packet::Sys::Commentary
|
100
|
+
when 5 then Packet::Unknown
|
101
|
+
when 6 then Packet::Sys::Notice
|
102
|
+
when 7 then Packet::Sys::Timestamp
|
103
|
+
when 8 then nil # Packet::Unknown
|
104
|
+
when 9 then Packet::Sys::Weather
|
105
|
+
when 10 then Packet::Sys::Speed
|
106
|
+
when 11 then Packet::Sys::TrackStatus
|
107
|
+
when 12 then Packet::Sys::Copyright
|
108
|
+
when 13 then nil # Packet::Unknown
|
109
|
+
when 14 then nil # Packet::Unknown
|
110
|
+
when 15 then nil # Packet::Unknown
|
111
|
+
end
|
112
|
+
end or raise UnexpectedPacket, "Unexpected #{car? ? 'car' : 'sys'} packet type #{packet_type.inspect} for #{Event::Type.name_for event_type} event"
|
113
|
+
end
|
114
|
+
|
115
|
+
class MissingEventType < RuntimeError
|
116
|
+
end
|
117
|
+
|
118
|
+
# An unexpected packet is one that we don't expect to appear in the data stream
|
119
|
+
class UnexpectedPacket < RuntimeError
|
120
|
+
end
|
121
|
+
|
122
|
+
# An unknown packet is one that we expect (from experience) to appear in the data stream but don't know its purpose
|
123
|
+
class UnknownPacket < RuntimeError
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module LiveF1
|
2
|
+
class Packet
|
3
|
+
module SectorTime
|
4
|
+
|
5
|
+
# Note of which bits appear in packet headers to represent different
|
6
|
+
# coloured sectors
|
7
|
+
# TODO: Use these colours somewhere
|
8
|
+
COLORS = {
|
9
|
+
0b010 => :red,
|
10
|
+
0b110 => :yellow,
|
11
|
+
0b001 => :white,
|
12
|
+
0b100 => :purple,
|
13
|
+
0b011 => :green
|
14
|
+
}
|
15
|
+
|
16
|
+
def seconds
|
17
|
+
if match = data.match(/^(?:(\d+):)?(\d+.\d+)$/)
|
18
|
+
_, minutes, seconds = match.to_a
|
19
|
+
(Rational(minutes.to_i * 60) + Rational(seconds)).to_f
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
seconds || data
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module LiveF1
|
4
|
+
class Packet
|
5
|
+
class Sys
|
6
|
+
class Commentary < Sys
|
7
|
+
include Packet::Type::Long
|
8
|
+
include Packet::Decryptable
|
9
|
+
|
10
|
+
# Is this the last line of this commentary string?
|
11
|
+
#
|
12
|
+
# If not, the next packet should also be a Commentary packet continuing this text
|
13
|
+
def terminal?
|
14
|
+
data.bytes.to_a[1] == 1
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the line of commentary, which may only be a partial line if
|
18
|
+
# this commentary was split over multiple packets
|
19
|
+
def line
|
20
|
+
# The commentary packet encoding used to be all messed up. Its UTF-8
|
21
|
+
# characters were treated as Windows-1252 and then reconverted back
|
22
|
+
# to UTF-8. We used to fix that but now the issue has been
|
23
|
+
# corrected.
|
24
|
+
data[2..-1].force_encoding("UTF-8")#.encode("Windows-1252").force_encoding("UTF-8")
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"%s%s" % [line, (terminal? ? "" : "…")]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|