px4_log_reader 0.0.7 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +4 -2
- data/lib/px4_log_reader/log_file.rb +85 -27
- data/lib/px4_log_reader/message_descriptor.rb +11 -2
- data/lib/px4_log_reader/progress.rb +56 -0
- data/lib/px4_log_reader/reader.rb +81 -28
- data/lib/px4_log_reader/version.rb +1 -1
- data/lib/px4_log_reader.rb +1 -0
- data/test/test_files/pixhawk_descriptor_cache.px4mc +0 -0
- data/test/test_files/test_log.px4log +0 -0
- data/test/test_log_file.rb +61 -60
- data/test/test_reader.rb +62 -53
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c75862bd918e0d1d080d9b7a4cb52d342ddd3b8
|
4
|
+
data.tar.gz: 2002ad3763f5a4cd976264036b7a7b7733aebbd7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c95423a6772f8ce0c6243bdb2b528d21b47f86784d91de05a154877db24a8a08cae4ddbff196b691fe84bc253785c5e3dec356a24ed1a4d7c377ad4ef75a38a1
|
7
|
+
data.tar.gz: b87364ae76b58ae13fa860358c8973fd68351fd328a1b99f117773ab53f5964f8be3851400a820ed382ea2e1da2d7f38a0aca37d624d482b6510110c7e325ebf
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -131,7 +131,7 @@ task :cut_log, :count, :skip, :filename do |t,args|
|
|
131
131
|
|
132
132
|
read_count = args[:count].to_i
|
133
133
|
read_count.times do
|
134
|
-
message = Px4LogReader::LogFile.read_message( input, message_descriptors )
|
134
|
+
message, offset = Px4LogReader::LogFile.read_message( input, message_descriptors )
|
135
135
|
Px4LogReader::LogFile.write_message( output, message )
|
136
136
|
end
|
137
137
|
end
|
@@ -148,7 +148,9 @@ task :dump, :filename do |t,args|
|
|
148
148
|
|
149
149
|
File.open( args[:filename], 'r' ) do |input|
|
150
150
|
count = 1
|
151
|
-
|
151
|
+
loop do
|
152
|
+
message, offset = Px4LogReader::LogFile.read_message( input, message_descriptors )
|
153
|
+
break if message.nil?
|
152
154
|
puts "#{count}) #{message.descriptor.name}, #{'%02X'%message.descriptor.type}"
|
153
155
|
count += 1
|
154
156
|
end
|
@@ -35,17 +35,32 @@ module Px4LogReader
|
|
35
35
|
module LogFile
|
36
36
|
|
37
37
|
HEADER_MARKER = [0xA3,0x95]
|
38
|
-
HEADER_LENGTH =
|
38
|
+
HEADER_LENGTH = HEADER_MARKER.length + 1
|
39
39
|
FORMAT_DESCRIPTOR_TABLE = { FORMAT_MESSAGE.type => FORMAT_MESSAGE }.freeze
|
40
40
|
|
41
|
-
|
41
|
+
@@debug = false
|
42
|
+
|
43
|
+
def self.enable_debug( enable )
|
44
|
+
@@debug = enable
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.read_descriptors( file, descriptor_cache=nil, &block )
|
42
48
|
|
43
49
|
message_descriptors = {}
|
44
50
|
|
45
|
-
|
46
|
-
|
47
|
-
|
51
|
+
loop do
|
52
|
+
|
53
|
+
message_descriptor, file_offset = read_descriptor( file )
|
54
|
+
|
55
|
+
if message_descriptor
|
56
|
+
if !message_descriptors.keys.include? message_descriptor.type
|
57
|
+
message_descriptors[ message_descriptor.type ] = message_descriptor
|
58
|
+
yield message_descriptor if block_given?
|
59
|
+
end
|
60
|
+
else
|
61
|
+
break
|
48
62
|
end
|
63
|
+
|
49
64
|
end
|
50
65
|
|
51
66
|
# If a cache filename was supplied, dump the descriptors to the cache
|
@@ -56,80 +71,108 @@ module Px4LogReader
|
|
56
71
|
return message_descriptors
|
57
72
|
end
|
58
73
|
|
59
|
-
def self.read_descriptor(
|
74
|
+
def self.read_descriptor( file, skip_corrupt=true, &block )
|
60
75
|
|
61
76
|
message_descriptor = nil
|
77
|
+
offset = nil
|
62
78
|
|
63
79
|
begin
|
64
80
|
|
65
|
-
descriptor_message = read_message(
|
81
|
+
descriptor_message, offset = read_message( file, FORMAT_DESCRIPTOR_TABLE, true, &block )
|
66
82
|
|
67
83
|
if descriptor_message
|
68
|
-
|
69
84
|
message_descriptor = Px4LogReader::MessageDescriptor.new
|
70
85
|
message_descriptor.from_message( descriptor_message )
|
71
|
-
|
72
86
|
end
|
73
87
|
|
74
88
|
rescue Px4LogReader::InvalidDescriptorError => error
|
75
89
|
|
90
|
+
puts "#{error.class}: #{error.message}"
|
91
|
+
puts error.backtrace.join("\n")
|
92
|
+
|
76
93
|
if skip_corrupt
|
77
94
|
retry
|
78
95
|
else
|
79
96
|
raise error
|
80
97
|
end
|
81
98
|
|
82
|
-
rescue StandardError =>
|
83
|
-
puts "#{
|
84
|
-
puts
|
99
|
+
rescue StandardError => error
|
100
|
+
puts "#{error.class}: #{error.message}"
|
101
|
+
puts error.backtrace.join("\n")
|
85
102
|
end
|
86
103
|
|
87
|
-
return message_descriptor
|
104
|
+
return message_descriptor, offset
|
88
105
|
end
|
89
106
|
|
90
|
-
def self.read_message(
|
107
|
+
def self.read_message( file, message_descriptors, stop_on_no_match = false, &block )
|
91
108
|
|
92
109
|
message = nil
|
93
|
-
|
94
|
-
|
110
|
+
offset = nil
|
111
|
+
eof = false
|
112
|
+
|
113
|
+
while message.nil? && !eof do
|
114
|
+
message_type, offset = read_message_header( file )
|
115
|
+
eof = message_type.nil?
|
95
116
|
|
96
117
|
if message_type
|
97
118
|
|
98
119
|
message_descriptor = message_descriptors[ message_type ]
|
99
120
|
|
121
|
+
yield offset if block_given?
|
122
|
+
|
100
123
|
if message_descriptor
|
101
|
-
|
124
|
+
|
125
|
+
message_data = file.read( message_descriptor.length - HEADER_LENGTH )
|
126
|
+
print_data( message_data ) if @@debug
|
102
127
|
message = message_descriptor.unpack_message( message_data )
|
103
|
-
end
|
104
128
|
|
105
|
-
|
106
|
-
|
129
|
+
elsif stop_on_no_match
|
130
|
+
|
131
|
+
# Seek back to the beginning of the header for the non-
|
132
|
+
# matching message.
|
133
|
+
file.seek( -1 * HEADER_LENGTH, IO::SEEK_CUR )
|
134
|
+
break
|
135
|
+
|
136
|
+
end
|
107
137
|
end
|
108
138
|
end
|
109
139
|
|
110
|
-
return message
|
140
|
+
return message, offset
|
111
141
|
end
|
112
142
|
|
113
|
-
|
143
|
+
#
|
144
|
+
# Read the next message header from the input stream. Returns the
|
145
|
+
# message type and current file offset. If no header is found, the
|
146
|
+
# returned message type is nil and the offset is the end of the file.
|
147
|
+
#
|
148
|
+
def self.read_message_header( file )
|
114
149
|
message_type = nil
|
150
|
+
offset = nil
|
151
|
+
drop_count = 0
|
115
152
|
|
116
153
|
begin
|
117
154
|
|
118
|
-
data
|
155
|
+
data = file.read_nonblock( HEADER_MARKER.length )
|
156
|
+
offset = file.pos
|
119
157
|
|
120
158
|
if data && ( data.length == HEADER_MARKER.length )
|
121
159
|
|
122
160
|
while !data.empty? && message_type.nil? do
|
123
161
|
|
124
|
-
if ( byte =
|
162
|
+
if ( byte = file.read_nonblock( 1 ) )
|
125
163
|
data << byte
|
126
164
|
end
|
165
|
+
offset = file.pos
|
127
166
|
|
128
167
|
if data.length >= ( HEADER_MARKER.length + 1 )
|
129
|
-
|
130
|
-
|
168
|
+
|
169
|
+
unpacked_data = data.unpack('C*')
|
170
|
+
|
171
|
+
if unpacked_data[ 0, HEADER_MARKER.length ] == HEADER_MARKER
|
172
|
+
message_type = unpacked_data.last & 0xFF
|
131
173
|
else
|
132
174
|
data = data[1..-1]
|
175
|
+
drop_count += 1
|
133
176
|
end
|
134
177
|
else
|
135
178
|
data = []
|
@@ -145,7 +188,7 @@ module Px4LogReader
|
|
145
188
|
puts error.backtrace.join("\n")
|
146
189
|
end
|
147
190
|
|
148
|
-
return message_type
|
191
|
+
return message_type, offset
|
149
192
|
end
|
150
193
|
|
151
194
|
|
@@ -155,6 +198,21 @@ module Px4LogReader
|
|
155
198
|
io.write( message.pack )
|
156
199
|
end
|
157
200
|
|
201
|
+
|
202
|
+
def self.print_data( buffer )
|
203
|
+
text = ''
|
204
|
+
buffer.unpack( 'C*' ).each_with_index do |byte,count|
|
205
|
+
if (count % 16) == 0
|
206
|
+
text << "#{count/16}: "
|
207
|
+
end
|
208
|
+
text << ('%02X'%byte) << ' '
|
209
|
+
if (((count+1) % 16) == 0)
|
210
|
+
text << "\n"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
puts text
|
214
|
+
end
|
215
|
+
|
158
216
|
end
|
159
217
|
|
160
218
|
end
|
@@ -34,6 +34,8 @@ module Px4LogReader
|
|
34
34
|
|
35
35
|
class MessageDescriptor
|
36
36
|
|
37
|
+
@@validation_level = :strict
|
38
|
+
|
37
39
|
attr_reader :name
|
38
40
|
attr_reader :type
|
39
41
|
attr_reader :length
|
@@ -85,9 +87,16 @@ module Px4LogReader
|
|
85
87
|
|
86
88
|
fields = fields_string.split(',')
|
87
89
|
|
88
|
-
if fields.length != @format.length
|
90
|
+
if (@@validation_level == :strict) && (fields.length != @format.length)
|
91
|
+
puts "fields = #{fields_string}"
|
92
|
+
puts "format = '#{@format}'"
|
93
|
+
puts "length = #{@length}"
|
94
|
+
puts "type = 0x#{'%02X'% @type}"
|
89
95
|
raise InvalidDescriptorError.new(
|
90
96
|
"Field count must match format length: expected #{@format.length}; found #{fields.length}")
|
97
|
+
elsif (@@validation_level == :lenient) && (fields.length > @format.length)
|
98
|
+
raise InvalidDescriptorError.new(
|
99
|
+
"Field count is greater than format length: expected #{@format.length}; found #{fields.length}")
|
91
100
|
else
|
92
101
|
@field_list = MessageDescriptor.build_field_list( fields )
|
93
102
|
end
|
@@ -209,7 +218,7 @@ module Px4LogReader
|
|
209
218
|
FORMAT_MESSAGE = Px4LogReader::MessageDescriptor.new({
|
210
219
|
name: 'FMT',
|
211
220
|
type: 0x80,
|
212
|
-
length:
|
221
|
+
length: 89, # header.size + B(1) + B(1) + n(4) + N(16) + Z(64)
|
213
222
|
format: 'BBnNZ',
|
214
223
|
fields: [ "Type", "Length", "Name", "Format", "Labels" ] }).freeze
|
215
224
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2016, Robert Glissmann
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
9
|
+
# list of conditions and the following disclaimer.
|
10
|
+
#
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
#
|
15
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
16
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
17
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
19
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
20
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
21
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
22
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
23
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
24
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
25
|
+
#
|
26
|
+
|
27
|
+
# %% license-end-token %%
|
28
|
+
#
|
29
|
+
# Author: Robert.Glissmann@gmail.com (Robert Glissmann)
|
30
|
+
#
|
31
|
+
#
|
32
|
+
|
33
|
+
module Px4LogReader
|
34
|
+
|
35
|
+
class Progress
|
36
|
+
|
37
|
+
attr_accessor :file_size
|
38
|
+
|
39
|
+
def initialize( active_file )
|
40
|
+
@active_file = active_file
|
41
|
+
|
42
|
+
active_file.seek( 0, IO::SEEK_END )
|
43
|
+
@file_size = active_file.pos
|
44
|
+
active_file.seek( 0 )
|
45
|
+
end
|
46
|
+
|
47
|
+
def file_offset
|
48
|
+
@active_file.pos
|
49
|
+
end
|
50
|
+
|
51
|
+
def percentage
|
52
|
+
return ( file_offset.to_f / @file_size.to_f )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -32,13 +32,20 @@
|
|
32
32
|
|
33
33
|
module Px4LogReader
|
34
34
|
|
35
|
-
|
35
|
+
# Attach a reader to an existing input stream.
|
36
|
+
#
|
37
|
+
# @param input_stream [IO] Valid input stream
|
38
|
+
# @param options [Hash] Reader options hash
|
39
|
+
# @param block Optional block
|
40
|
+
#
|
41
|
+
def self.attach( input_stream, options, &block )
|
36
42
|
|
37
|
-
reader = Reader.new(
|
43
|
+
reader = Reader.new( input_stream, options )
|
38
44
|
|
39
45
|
yield reader if block_given?
|
40
46
|
|
41
47
|
return reader
|
48
|
+
|
42
49
|
end
|
43
50
|
|
44
51
|
def self.open( filename, options = {}, &block )
|
@@ -46,7 +53,7 @@ module Px4LogReader
|
|
46
53
|
reader = nil
|
47
54
|
|
48
55
|
if File.exist?( filename )
|
49
|
-
reader = self.
|
56
|
+
reader = self.attach( File.open( filename, 'rb' ), options, &block )
|
50
57
|
end
|
51
58
|
|
52
59
|
return reader
|
@@ -57,7 +64,7 @@ module Px4LogReader
|
|
57
64
|
reader = nil
|
58
65
|
|
59
66
|
if File.exist?( filename )
|
60
|
-
reader = self.
|
67
|
+
reader = self.attach( File.open( filename, 'rb' ), options, &block )
|
61
68
|
else
|
62
69
|
raise FileNotFoundError.new( filename )
|
63
70
|
end
|
@@ -65,11 +72,19 @@ module Px4LogReader
|
|
65
72
|
return reader
|
66
73
|
end
|
67
74
|
|
75
|
+
# Container to hold the most recent copy of each message type
|
68
76
|
class Context
|
77
|
+
|
69
78
|
attr_reader :messages
|
79
|
+
|
70
80
|
def initialize
|
71
|
-
messages = {}
|
81
|
+
@messages = {}
|
72
82
|
end
|
83
|
+
|
84
|
+
# Query the context for the most recent copy of a message by name
|
85
|
+
#
|
86
|
+
# @param name [String] Message name
|
87
|
+
#
|
73
88
|
def find_by_name( name )
|
74
89
|
named_message = nil
|
75
90
|
@messages.values.each do |message|
|
@@ -79,40 +94,62 @@ module Px4LogReader
|
|
79
94
|
end
|
80
95
|
return named_message
|
81
96
|
end
|
97
|
+
|
98
|
+
# Query the context for the most recent copy of a message by type
|
99
|
+
#
|
100
|
+
# @param type [Fixnum] Message type
|
101
|
+
#
|
82
102
|
def find_by_type( type )
|
83
103
|
return @messages[ type ]
|
84
104
|
end
|
105
|
+
|
106
|
+
# Set the most recent copy of a message by type. Any existing message
|
107
|
+
# is overwritten.
|
108
|
+
#
|
109
|
+
# @param message [LogMessage] Message instance
|
110
|
+
#
|
85
111
|
def set( message )
|
86
112
|
@messages[ message.descriptor.type ] = message.dup
|
87
113
|
end
|
114
|
+
|
88
115
|
end
|
89
116
|
|
90
|
-
class Reader
|
117
|
+
class Reader
|
118
|
+
|
119
|
+
attr_reader :progress
|
120
|
+
attr_reader :context
|
91
121
|
|
92
122
|
def initialize( file, options )
|
93
123
|
|
94
124
|
opts = {
|
95
125
|
cache_filename: '',
|
96
|
-
buffer_size_kb: 10 * 1024
|
97
126
|
}.merge( options )
|
98
127
|
|
99
128
|
@message_descriptors = {}
|
100
|
-
@buffers = LogBufferArray.new
|
101
129
|
@descriptor_cache = nil
|
102
130
|
@context = Context.new
|
103
131
|
|
104
132
|
@log_file = file
|
105
|
-
|
133
|
+
@progress = Progress.new( @log_file )
|
106
134
|
|
107
135
|
@descriptor_cache = MessageDescriptorCache.new( opts[:cache_filename] )
|
108
136
|
end
|
109
137
|
|
110
|
-
|
138
|
+
#
|
139
|
+
# Get the list of descriptors associated with the open PX4 log file.
|
140
|
+
# If a valid descriptor cache was specified at startup, the descriptors
|
141
|
+
# are loaded from the cache. Otherwise, the descriptors are parsed from
|
142
|
+
# the open log.
|
143
|
+
#
|
144
|
+
# @param [block] optional block is passed each descriptor as it is read
|
145
|
+
# @return descriptors [Array] Array of descriptors
|
146
|
+
#
|
147
|
+
def descriptors( &block )
|
111
148
|
if @log_file && @message_descriptors.empty?
|
112
149
|
if @descriptor_cache && @descriptor_cache.exist?
|
113
150
|
@message_descriptors = @descriptor_cache.read_descriptors
|
114
151
|
else
|
115
|
-
@message_descriptors = LogFile::read_descriptors( @log_file, @descriptor_cache )
|
152
|
+
@message_descriptors = LogFile::read_descriptors( @log_file, @descriptor_cache, &block )
|
116
153
|
end
|
117
154
|
|
118
155
|
@message_descriptors[ FORMAT_MESSAGE.type ] = FORMAT_MESSAGE
|
@@ -121,6 +158,16 @@ module Px4LogReader
|
|
121
158
|
return @message_descriptors
|
122
159
|
end
|
123
160
|
|
161
|
+
#
|
162
|
+
# Iterate over all log messages. Embedded message descriptors are skipped.
|
163
|
+
# If a "with" list is supplied, only messages in the list are passed to
|
164
|
+
# the caller-supplied block. If a "without" list supplied, all messages
|
165
|
+
# except those in the list are passed to the caller-supplied block. The
|
166
|
+
# caller must supply a block.
|
167
|
+
#
|
168
|
+
# @param options [Hash] options
|
169
|
+
# @param block [Block] block takes message as argument
|
170
|
+
#
|
124
171
|
def each_message( options = {}, &block )
|
125
172
|
|
126
173
|
opts ={
|
@@ -131,7 +178,11 @@ module Px4LogReader
|
|
131
178
|
opts[:with].map! do |val|
|
132
179
|
if val.class == String
|
133
180
|
descriptor = descriptors.values.find { |desc| desc.name == val }
|
134
|
-
|
181
|
+
if descriptor
|
182
|
+
val = descriptor.type
|
183
|
+
else
|
184
|
+
puts "Failed to find descriptor with name '#{val}'"
|
185
|
+
end
|
135
186
|
end
|
136
187
|
end
|
137
188
|
|
@@ -151,19 +202,19 @@ module Px4LogReader
|
|
151
202
|
|
152
203
|
loop do
|
153
204
|
|
154
|
-
message = LogFile::read_message( @log_file, @message_descriptors )
|
205
|
+
message, offset = LogFile::read_message( @log_file, @message_descriptors )
|
155
206
|
break if message.nil?
|
156
207
|
|
157
|
-
#
|
208
|
+
# Add message to the set of latest messages.
|
158
209
|
@context.set( message )
|
159
210
|
|
160
211
|
if opts[:with].empty?
|
161
|
-
if !opts[:without].include?( message.descriptor.
|
162
|
-
yield message
|
212
|
+
if !opts[:without].include?( message.descriptor.type )
|
213
|
+
yield message
|
163
214
|
end
|
164
215
|
else
|
165
216
|
if opts[:with].include?( message.descriptor.type )
|
166
|
-
yield message
|
217
|
+
yield message
|
167
218
|
end
|
168
219
|
end
|
169
220
|
|
@@ -175,17 +226,19 @@ module Px4LogReader
|
|
175
226
|
|
176
227
|
end
|
177
228
|
|
178
|
-
#
|
179
|
-
#
|
180
|
-
|
181
|
-
#
|
182
|
-
#
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
229
|
+
#
|
230
|
+
# Seek to the specified file offset. If the offset is greater than the
|
231
|
+
# file size, seeks to the end of the file.
|
232
|
+
#
|
233
|
+
# @param offset [Fixnum] File offset in bytes
|
234
|
+
#
|
235
|
+
def seek( offset )
|
236
|
+
if @log_file
|
237
|
+
seek_offset = offset
|
238
|
+
seek_offset = @progress.file_size if offset > @progress.file_size
|
239
|
+
@log_file.seek( seek_offset, IO::SEEK_SET )
|
240
|
+
end
|
241
|
+
end
|
189
242
|
|
190
243
|
end
|
191
244
|
|
data/lib/px4_log_reader.rb
CHANGED
@@ -37,6 +37,7 @@ require 'px4_log_reader/invalid_descriptor_error'
|
|
37
37
|
require 'px4_log_reader/log_message'
|
38
38
|
require 'px4_log_reader/message_descriptor'
|
39
39
|
require 'px4_log_reader/log_buffer'
|
40
|
+
require 'px4_log_reader/progress'
|
40
41
|
require 'px4_log_reader/log_file'
|
41
42
|
require 'px4_log_reader/message_descriptor_cache'
|
42
43
|
require 'px4_log_reader/reader'
|
Binary file
|
Binary file
|
data/test/test_log_file.rb
CHANGED
@@ -41,96 +41,67 @@ class TestLogFile < MiniTest::Test
|
|
41
41
|
def teardown
|
42
42
|
end
|
43
43
|
|
44
|
-
class MockFileIO
|
45
|
-
|
46
|
-
attr_reader :buffer
|
47
|
-
|
48
|
-
def initialize
|
49
|
-
@buffer = ''
|
50
|
-
end
|
51
|
-
|
52
|
-
def reset
|
53
|
-
@buffer = ''
|
54
|
-
end
|
55
|
-
|
56
|
-
def read( byte_count = nil )
|
57
|
-
data = nil
|
58
|
-
|
59
|
-
if byte_count >= @buffer.size
|
60
|
-
data = @buffer.dup
|
61
|
-
@buffer = ''
|
62
|
-
else
|
63
|
-
data = @buffer[ 0, byte_count ].dup
|
64
|
-
@buffer = @buffer[ byte_count .. -1 ]
|
65
|
-
end
|
66
|
-
|
67
|
-
data
|
68
|
-
end
|
69
|
-
|
70
|
-
def read_nonblock( byte_count = nil )
|
71
|
-
return read( byte_count )
|
72
|
-
end
|
73
|
-
|
74
|
-
def write( data )
|
75
|
-
@buffer << data
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
44
|
def test_read_header
|
81
45
|
|
82
46
|
# First, test that read_message_header returns null if the file is exhausted
|
83
47
|
# without finding a message header.
|
84
|
-
mock_io
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
assert_equal true, mock_io.buffer.empty?
|
48
|
+
mock_io( rand_data( 256 ).pack('C*') ) do |writer,reader|
|
49
|
+
assert_equal [ nil, 256 ], Px4LogReader::LogFile.read_message_header( reader )
|
50
|
+
assert_equal 256, reader.pos
|
51
|
+
end
|
89
52
|
|
90
53
|
|
91
54
|
# Insert a message header with type = 0x80 into the middle of the data.
|
92
|
-
mock_io = MockFileIO.new
|
93
|
-
|
94
55
|
test_data = rand_data( 256 )
|
95
56
|
test_data.concat( Px4LogReader::LogFile::HEADER_MARKER )
|
96
57
|
test_data.concat( [ Px4LogReader::FORMAT_MESSAGE.type ] )
|
97
|
-
test_data.concat( rand_data( 256 ) )
|
98
|
-
mock_io
|
58
|
+
test_data.concat( rand_data( 256 ) )
|
59
|
+
mock_io( test_data.pack('C*') ) do |writer,reader|
|
99
60
|
|
100
|
-
|
101
|
-
assert_equal 256, mock_io.buffer.size
|
61
|
+
message_type, offset = Px4LogReader::LogFile.read_message_header( reader )
|
102
62
|
|
103
|
-
|
104
|
-
|
63
|
+
assert_equal Px4LogReader::FORMAT_MESSAGE.type, message_type
|
64
|
+
assert_equal 256 + Px4LogReader::LogFile::HEADER_LENGTH, offset
|
105
65
|
|
66
|
+
message_type, offset = Px4LogReader::LogFile.read_message_header( reader )
|
67
|
+
assert_nil message_type
|
68
|
+
assert_equal true, reader.eof?
|
69
|
+
assert_equal test_data.size, offset
|
70
|
+
|
71
|
+
end
|
106
72
|
|
107
|
-
# Test with a buffer that ends in a message header sync pattern.
|
108
|
-
mock_io = MockFileIO.new
|
109
73
|
|
74
|
+
# Test with a buffer that ends in a message header sync pattern.
|
110
75
|
test_data = rand_data( 64 )
|
111
76
|
test_data.concat( Px4LogReader::LogFile::HEADER_MARKER )
|
112
|
-
mock_io
|
77
|
+
mock_io( test_data.pack('C*') ) do |writer,reader|
|
113
78
|
|
114
|
-
|
115
|
-
|
79
|
+
message_type, offset = Px4LogReader::LogFile.read_message_header( reader )
|
80
|
+
assert_nil message_type
|
81
|
+
assert_equal true, reader.eof?
|
82
|
+
assert_equal test_data.size, offset
|
83
|
+
|
84
|
+
end
|
116
85
|
|
117
86
|
end
|
118
87
|
|
119
88
|
def test_write_message
|
120
89
|
|
121
|
-
mock_io = MockFileIO.new
|
122
|
-
|
123
90
|
fields = [ 0x24, 32, 'test', 'IIbMh', 'id,counts,flag,length,ord' ]
|
124
91
|
|
125
92
|
message = Px4LogReader::LogMessage.new(
|
126
93
|
Px4LogReader::FORMAT_MESSAGE,
|
127
94
|
fields )
|
128
95
|
|
129
|
-
|
96
|
+
mock_io do |writer,reader|
|
97
|
+
Px4LogReader::LogFile.write_message( writer, message )
|
130
98
|
|
131
|
-
|
132
|
-
|
133
|
-
|
99
|
+
expected_length = Px4LogReader::FORMAT_MESSAGE.length
|
100
|
+
assert_equal expected_length, writer.pos
|
101
|
+
|
102
|
+
header = reader.read( 3 )
|
103
|
+
assert_equal [ 0xA3, 0x95, 0x80 ], header.unpack('C*')
|
104
|
+
end
|
134
105
|
|
135
106
|
end
|
136
107
|
|
@@ -155,4 +126,34 @@ class TestLogFile < MiniTest::Test
|
|
155
126
|
return test_data
|
156
127
|
end
|
157
128
|
|
129
|
+
def mock_io( buffer = nil )
|
130
|
+
|
131
|
+
temp_filename = File.join( 'test', 'temp_test_data', 'mock_io.dat' )
|
132
|
+
unless Dir.exist?( File.dirname( temp_filename ) )
|
133
|
+
FileUtils.mkdir( File.dirname( temp_filename ) )
|
134
|
+
end
|
135
|
+
|
136
|
+
# Create the file.
|
137
|
+
FileUtils.touch( temp_filename )
|
138
|
+
|
139
|
+
# Open the file for writing and reading and writing.
|
140
|
+
writer = File.open( temp_filename, 'wb' )
|
141
|
+
if buffer
|
142
|
+
writer.write( buffer )
|
143
|
+
writer.flush
|
144
|
+
end
|
145
|
+
|
146
|
+
reader = File.open( temp_filename, 'rb' )
|
147
|
+
|
148
|
+
yield writer, reader if block_given?
|
149
|
+
|
150
|
+
reader.close
|
151
|
+
writer.close
|
152
|
+
|
153
|
+
if Dir.exist?( File.dirname( temp_filename ) )
|
154
|
+
FileUtils.rm_rf( File.dirname( temp_filename ) )
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
158
159
|
end
|
data/test/test_reader.rb
CHANGED
@@ -84,36 +84,56 @@ class TestReader < MiniTest::Test
|
|
84
84
|
# The log file associated with this test case contains the following
|
85
85
|
# messages in the specified order. Validate the order and context.
|
86
86
|
#
|
87
|
-
# 1
|
88
|
-
# 2
|
89
|
-
# 3
|
90
|
-
# 4
|
91
|
-
# 5
|
92
|
-
# 6
|
93
|
-
# 7
|
87
|
+
# 1: LPOS
|
88
|
+
# 2: GPOS
|
89
|
+
# 3: BATT
|
90
|
+
# 4: PWR
|
91
|
+
# 5: EST0
|
92
|
+
# 6: EST1
|
93
|
+
# 7: EST2
|
94
|
+
# 8: EST3
|
95
|
+
# 9: CTS
|
96
|
+
# 10: ATT
|
97
|
+
# 11: TIME
|
98
|
+
# 12: IMU
|
99
|
+
# 13: SENS
|
100
|
+
# 14: IMU1
|
101
|
+
# 15: ATSP
|
102
|
+
# 16: ARSP
|
103
|
+
# 17: OUT0
|
104
|
+
# 18: ATTC
|
94
105
|
#
|
95
106
|
index = 0
|
96
|
-
|
97
|
-
|
107
|
+
expected_messages = [
|
108
|
+
['LPOS',0x06],
|
109
|
+
['GPOS',0x10],
|
110
|
+
['BATT',0x14],
|
111
|
+
['PWR',0x18],
|
112
|
+
['EST0',0x20],
|
113
|
+
['EST1',0x21],
|
114
|
+
['EST2',0x22]]
|
98
115
|
|
99
|
-
reader.each_message do |message
|
116
|
+
reader.each_message do |message|
|
100
117
|
|
101
|
-
|
102
|
-
|
118
|
+
# puts "#{index+1}: #{message.descriptor.name}, #{'%02X'%message.descriptor.type}"
|
119
|
+
break if index >= expected_messages.size
|
120
|
+
|
121
|
+
expected_name = expected_messages[ index ][0]
|
122
|
+
expected_type = expected_messages[ index ][1]
|
103
123
|
|
104
124
|
assert_equal expected_name, message.descriptor.name
|
105
125
|
assert_equal expected_type, message.descriptor.type
|
106
126
|
|
107
|
-
context_message = context.find_by_name( expected_name )
|
127
|
+
context_message = reader.context.find_by_name( expected_name )
|
108
128
|
refute_nil context_message
|
109
129
|
assert_equal expected_name, context_message.descriptor.name
|
110
130
|
|
111
|
-
context_message = context.find_by_type( expected_type )
|
131
|
+
context_message = reader.context.find_by_type( expected_type )
|
112
132
|
refute_nil context_message
|
113
133
|
assert_equal expected_type, context_message.descriptor.type
|
114
134
|
|
115
135
|
index += 1
|
116
|
-
assert_equal index, context.messages.size
|
136
|
+
assert_equal index, reader.context.messages.size
|
117
137
|
|
118
138
|
end
|
119
139
|
|
@@ -133,40 +153,36 @@ class TestReader < MiniTest::Test
|
|
133
153
|
|
134
154
|
log_file_opened = true
|
135
155
|
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
# 1) TIME
|
140
|
-
# 2) STAT
|
141
|
-
# 3) IMU
|
142
|
-
# 4) SENS
|
143
|
-
# 5) IMU1
|
144
|
-
# 6) VTOL
|
145
|
-
# 7) GPS
|
146
|
-
#
|
156
|
+
# See test_reader for the un-filtered list of messages. This test
|
157
|
+
# expects the same list, minus the GPOS and EST1 messages.
|
147
158
|
|
148
159
|
index = 0
|
149
|
-
|
150
|
-
|
160
|
+
expected_messages = [
|
161
|
+
['LPOS',0x06],
|
162
|
+
['BATT',0x14],
|
163
|
+
['PWR',0x18],
|
164
|
+
['EST0',0x20],
|
165
|
+
['EST2',0x22]]
|
166
|
+
|
167
|
+
reader.each_message( { without: ['GPOS','EST1'] } ) do |message|
|
151
168
|
|
152
|
-
|
169
|
+
break if index >= expected_messages.size
|
153
170
|
|
154
|
-
expected_name =
|
155
|
-
expected_type =
|
171
|
+
expected_name = expected_messages[ index ][0]
|
172
|
+
expected_type = expected_messages[ index ][1]
|
156
173
|
|
157
174
|
assert_equal expected_name, message.descriptor.name
|
158
175
|
assert_equal expected_type, message.descriptor.type
|
159
176
|
|
160
|
-
context_message = context.find_by_name( expected_name )
|
177
|
+
context_message = reader.context.find_by_name( expected_name )
|
161
178
|
refute_nil context_message
|
162
179
|
assert_equal expected_name, context_message.descriptor.name
|
163
180
|
|
164
|
-
context_message = context.find_by_type( expected_type )
|
181
|
+
context_message = reader.context.find_by_type( expected_type )
|
165
182
|
refute_nil context_message
|
166
183
|
assert_equal expected_type, context_message.descriptor.type
|
167
184
|
|
168
185
|
index += 1
|
169
|
-
assert_equal index, context.messages.size
|
170
186
|
|
171
187
|
end
|
172
188
|
|
@@ -184,40 +200,33 @@ class TestReader < MiniTest::Test
|
|
184
200
|
|
185
201
|
log_file_opened = true
|
186
202
|
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
# 1) TIME
|
191
|
-
# 2) STAT
|
192
|
-
# 3) IMU
|
193
|
-
# 4) SENS
|
194
|
-
# 5) IMU1
|
195
|
-
# 6) VTOL
|
196
|
-
# 7) GPS
|
197
|
-
#
|
203
|
+
# See test_reader for the un-filtered list of messages. This test
|
204
|
+
# expects just the GPOS and PWR messages.
|
198
205
|
|
199
206
|
index = 0
|
200
|
-
|
201
|
-
|
207
|
+
expected_messages = [
|
208
|
+
['GPOS',0x10],
|
209
|
+
['PWR',0x18]]
|
210
|
+
|
211
|
+
reader.each_message( { with: ['GPOS','PWR'] } ) do |message|
|
202
212
|
|
203
|
-
|
213
|
+
break if index >= expected_messages.size
|
204
214
|
|
205
|
-
expected_name =
|
206
|
-
expected_type =
|
215
|
+
expected_name = expected_messages[ index ][0]
|
216
|
+
expected_type = expected_messages[ index ][1]
|
207
217
|
|
208
218
|
assert_equal expected_name, message.descriptor.name
|
209
219
|
assert_equal expected_type, message.descriptor.type
|
210
220
|
|
211
|
-
context_message = context.find_by_name( expected_name )
|
221
|
+
context_message = reader.context.find_by_name( expected_name )
|
212
222
|
refute_nil context_message
|
213
223
|
assert_equal expected_name, context_message.descriptor.name
|
214
224
|
|
215
|
-
context_message = context.find_by_type( expected_type )
|
225
|
+
context_message = reader.context.find_by_type( expected_type )
|
216
226
|
refute_nil context_message
|
217
227
|
assert_equal expected_type, context_message.descriptor.type
|
218
228
|
|
219
229
|
index += 1
|
220
|
-
assert_equal index, context.messages.size
|
221
230
|
|
222
231
|
end
|
223
232
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: px4_log_reader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Glissmann
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- lib/px4_log_reader/log_message.rb
|
47
47
|
- lib/px4_log_reader/message_descriptor.rb
|
48
48
|
- lib/px4_log_reader/message_descriptor_cache.rb
|
49
|
+
- lib/px4_log_reader/progress.rb
|
49
50
|
- lib/px4_log_reader/reader.rb
|
50
51
|
- lib/px4_log_reader/version.rb
|
51
52
|
- px4_log_reader.gemspec
|