px4_log_reader 0.0.7 → 1.0.2
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.
- 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
|