tdms 0.1.0

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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +10 -0
  3. data/LICENSE.txt +25 -0
  4. data/README.md +34 -0
  5. data/Rakefile +15 -0
  6. data/demo.rb +16 -0
  7. data/doc/data_types.txt +23 -0
  8. data/doc/example_disasm.txt +47 -0
  9. data/doc/tdms_format.txt +101 -0
  10. data/doc/usage.txt +46 -0
  11. data/lib/tdms.rb +8 -0
  12. data/lib/tdms/aggregate.rb +71 -0
  13. data/lib/tdms/channel.rb +100 -0
  14. data/lib/tdms/datatypes.rb +173 -0
  15. data/lib/tdms/document.rb +114 -0
  16. data/lib/tdms/path.rb +58 -0
  17. data/lib/tdms/property.rb +17 -0
  18. data/lib/tdms/segment.rb +12 -0
  19. data/lib/tdms/streaming.rb +82 -0
  20. data/test/build_fixtures/README.txt +25 -0
  21. data/test/build_fixtures/type_01_int8_one_segment.vi +0 -0
  22. data/test/build_fixtures/type_01_int8_three_segments.vi +0 -0
  23. data/test/build_fixtures/type_01_int8_two_channels_one_segment.vi +0 -0
  24. data/test/build_fixtures/type_02_int16_one_segment.vi +0 -0
  25. data/test/build_fixtures/type_02_int16_three_segments.vi +0 -0
  26. data/test/build_fixtures/type_02_int16_two_channels_one_segment.vi +0 -0
  27. data/test/build_fixtures/type_03_int32_one_segment.vi +0 -0
  28. data/test/build_fixtures/type_03_int32_three_segments.vi +0 -0
  29. data/test/build_fixtures/type_03_int32_two_channels_one_segment.vi +0 -0
  30. data/test/build_fixtures/type_04_int64_one_segment.vi +0 -0
  31. data/test/build_fixtures/type_04_int64_three_segments.vi +0 -0
  32. data/test/build_fixtures/type_04_int64_two_channels_one_segment.vi +0 -0
  33. data/test/build_fixtures/type_05_uint8_one_segment.vi +0 -0
  34. data/test/build_fixtures/type_05_uint8_three_segments.vi +0 -0
  35. data/test/build_fixtures/type_05_uint8_two_channels_one_segment.vi +0 -0
  36. data/test/build_fixtures/type_06_uint16_one_segment.vi +0 -0
  37. data/test/build_fixtures/type_06_uint16_three_segments.vi +0 -0
  38. data/test/build_fixtures/type_06_uint16_two_channels_one_segment.vi +0 -0
  39. data/test/build_fixtures/type_07_uint32_one_segment.vi +0 -0
  40. data/test/build_fixtures/type_07_uint32_three_segments.vi +0 -0
  41. data/test/build_fixtures/type_07_uint32_two_channels_one_segment.vi +0 -0
  42. data/test/build_fixtures/type_08_uint64_one_segment.vi +0 -0
  43. data/test/build_fixtures/type_08_uint64_three_segments.vi +0 -0
  44. data/test/build_fixtures/type_08_uint64_two_channels_one_segment.vi +0 -0
  45. data/test/build_fixtures/type_09_single_one_segment.vi +0 -0
  46. data/test/build_fixtures/type_09_single_three_segments.vi +0 -0
  47. data/test/build_fixtures/type_09_single_two_channels_one_segment.vi +0 -0
  48. data/test/build_fixtures/type_0a_double_one_segment.vi +0 -0
  49. data/test/build_fixtures/type_0a_double_three_segments.vi +0 -0
  50. data/test/build_fixtures/type_0a_double_two_channels_one_segment.vi +0 -0
  51. data/test/build_fixtures/type_20_string_one_segment.vi +0 -0
  52. data/test/build_fixtures/type_20_string_three_segments.vi +0 -0
  53. data/test/build_fixtures/type_20_string_two_channels_one_segment.vi +0 -0
  54. data/test/build_fixtures/type_21_boolean_one_segment.vi +0 -0
  55. data/test/build_fixtures/type_21_boolean_three_segments.vi +0 -0
  56. data/test/build_fixtures/type_21_boolean_two_channels_one_segment.vi +0 -0
  57. data/test/build_fixtures/type_44_datetime_one_segment.vi +0 -0
  58. data/test/build_fixtures/type_44_timestamp_three_segments.vi +0 -0
  59. data/test/build_fixtures/type_44_timestamp_two_channels_one_segment.vi +0 -0
  60. data/test/fixtures/example.tdms +0 -0
  61. data/test/fixtures/type_01_int8_one_segment.tdms +0 -0
  62. data/test/fixtures/type_01_int8_three_segments.tdms +0 -0
  63. data/test/fixtures/type_01_int8_two_channels_one_segment.tdms +0 -0
  64. data/test/fixtures/type_02_int16_one_segment.tdms +0 -0
  65. data/test/fixtures/type_02_int16_three_segments.tdms +0 -0
  66. data/test/fixtures/type_02_int16_two_channels_one_segment.tdms +0 -0
  67. data/test/fixtures/type_03_int32_one_segment.tdms +0 -0
  68. data/test/fixtures/type_03_int32_three_segments.tdms +0 -0
  69. data/test/fixtures/type_03_int32_two_channels_one_segment.tdms +0 -0
  70. data/test/fixtures/type_04_int64_one_segment.tdms +0 -0
  71. data/test/fixtures/type_04_int64_three_segments.tdms +0 -0
  72. data/test/fixtures/type_04_int64_two_channels_one_segment.tdms +0 -0
  73. data/test/fixtures/type_05_uint8_one_segment.tdms +0 -0
  74. data/test/fixtures/type_05_uint8_three_segments.tdms +0 -0
  75. data/test/fixtures/type_05_uint8_two_channels_one_segment.tdms +0 -0
  76. data/test/fixtures/type_06_uint16_one_segment.tdms +0 -0
  77. data/test/fixtures/type_06_uint16_three_segments.tdms +0 -0
  78. data/test/fixtures/type_06_uint16_two_channels_one_segment.tdms +0 -0
  79. data/test/fixtures/type_07_uint32_one_segment.tdms +0 -0
  80. data/test/fixtures/type_07_uint32_three_segments.tdms +0 -0
  81. data/test/fixtures/type_07_uint32_two_channels_one_segment.tdms +0 -0
  82. data/test/fixtures/type_08_uint64_one_segment.tdms +0 -0
  83. data/test/fixtures/type_08_uint64_three_segments.tdms +0 -0
  84. data/test/fixtures/type_08_uint64_two_channels_one_segment.tdms +0 -0
  85. data/test/fixtures/type_09_single_one_segment.tdms +0 -0
  86. data/test/fixtures/type_09_single_three_segments.tdms +0 -0
  87. data/test/fixtures/type_09_single_two_channels_one_segment.tdms +0 -0
  88. data/test/fixtures/type_0a_double_one_segment.tdms +0 -0
  89. data/test/fixtures/type_0a_double_three_segments.tdms +0 -0
  90. data/test/fixtures/type_0a_double_two_channels_one_segment.tdms +0 -0
  91. data/test/fixtures/type_19_single_with_unit_one_segment.tdms +0 -0
  92. data/test/fixtures/type_19_single_with_unit_three_segments.tdms +0 -0
  93. data/test/fixtures/type_19_single_with_unit_two_channels_one_segment.tdms +0 -0
  94. data/test/fixtures/type_1a_double_with_unit_one_segment.tdms +0 -0
  95. data/test/fixtures/type_1a_double_with_unit_three_segments.tdms +0 -0
  96. data/test/fixtures/type_1a_double_with_unit_two_channels_one_segment.tdms +0 -0
  97. data/test/fixtures/type_20_double_two_channels_one_segment.tdms +0 -0
  98. data/test/fixtures/type_20_string_one_segment.tdms +0 -0
  99. data/test/fixtures/type_20_string_three_segments.tdms +0 -0
  100. data/test/fixtures/type_20_string_two_channels_one_segment.tdms +0 -0
  101. data/test/fixtures/type_20_string_two_segments.tdms +0 -0
  102. data/test/fixtures/type_21_boolean_one_segment.tdms +0 -0
  103. data/test/fixtures/type_21_boolean_three_segments.tdms +0 -0
  104. data/test/fixtures/type_21_boolean_two_channels_one_segment.tdms +0 -0
  105. data/test/fixtures/type_44_timestamp_one_segment.tdms +0 -0
  106. data/test/fixtures/type_44_timestamp_three_segments.tdms +0 -0
  107. data/test/fixtures/type_44_timestamp_two_channels_one_segment.tdms +0 -0
  108. data/test/read_type_01_int8_test.rb +58 -0
  109. data/test/read_type_02_int16_test.rb +58 -0
  110. data/test/read_type_03_int32_test.rb +58 -0
  111. data/test/read_type_04_int64_test.rb +64 -0
  112. data/test/read_type_05_uint8_test.rb +56 -0
  113. data/test/read_type_06_uint16_test.rb +58 -0
  114. data/test/read_type_07_uint32_test.rb +58 -0
  115. data/test/read_type_08_uint64_test.rb +58 -0
  116. data/test/read_type_09_single_test.rb +58 -0
  117. data/test/read_type_0a_double_test.rb +58 -0
  118. data/test/read_type_19_single_with_unit_test.rb +58 -0
  119. data/test/read_type_1a_double_with_unit_test.rb +58 -0
  120. data/test/read_type_20_string_test.rb +60 -0
  121. data/test/read_type_21_boolean_test.rb +56 -0
  122. data/test/read_type_44_timestamp_test.rb +60 -0
  123. data/test/test_helper.rb +9 -0
  124. metadata +284 -0
@@ -0,0 +1,173 @@
1
+ module Tdms
2
+
3
+ module DataType
4
+
5
+ class Base
6
+ attr_accessor :value
7
+
8
+ def initialize(value=nil)
9
+ @value = value
10
+ end
11
+ end
12
+
13
+ class Int8 < Base
14
+ Id = 0x01
15
+ LengthInBytes = 1
16
+
17
+ def self.read_from_stream(tdms_file)
18
+ new(tdms_file.read_i8)
19
+ end
20
+ end
21
+
22
+ class Int16 < Base
23
+ Id = 0x02
24
+ LengthInBytes = 2
25
+
26
+ def self.read_from_stream(tdms_file)
27
+ new(tdms_file.read_i16)
28
+ end
29
+ end
30
+
31
+ class Int32 < Base
32
+ Id = 0x03
33
+ LengthInBytes = 4
34
+
35
+ def self.read_from_stream(tdms_file)
36
+ new(tdms_file.read_i32)
37
+ end
38
+ end
39
+
40
+ class Int64 < Base
41
+ Id = 0x04
42
+ LengthInBytes = 8
43
+
44
+ def self.read_from_stream(tdms_file)
45
+ new(tdms_file.read_i64)
46
+ end
47
+ end
48
+
49
+ class Uint8 < Base
50
+ Id = 0x05
51
+ LengthInBytes = 1
52
+
53
+ def self.read_from_stream(tdms_file)
54
+ new(tdms_file.read_u8)
55
+ end
56
+ end
57
+
58
+ class Uint16 < Base
59
+ Id = 0x06
60
+ LengthInBytes = 2
61
+
62
+ def self.read_from_stream(tdms_file)
63
+ new(tdms_file.read_u16)
64
+ end
65
+ end
66
+
67
+ class Uint32 < Base
68
+ Id = 0x07
69
+ LengthInBytes = 4
70
+
71
+ def self.read_from_stream(tdms_file)
72
+ new(tdms_file.read_u32)
73
+ end
74
+ end
75
+
76
+ class Uint64 < Base
77
+ Id = 0x08
78
+ LengthInBytes = 8
79
+
80
+ def self.read_from_stream(tdms_file)
81
+ new(tdms_file.read_u64)
82
+ end
83
+ end
84
+
85
+ class Single < Base
86
+ Id = 0x09
87
+ LengthInBytes = 4
88
+
89
+ def self.read_from_stream(tdms_file)
90
+ new(tdms_file.read_single)
91
+ end
92
+ end
93
+
94
+ class Double < Base
95
+ Id = 0x0A
96
+ LengthInBytes = 8
97
+
98
+ def self.read_from_stream(tdms_file)
99
+ new(tdms_file.read_double)
100
+ end
101
+ end
102
+
103
+ class SingleWithUnit < Base
104
+ Id = 0x19
105
+ LengthInBytes = 4
106
+
107
+ def self.read_from_stream(tdms_file)
108
+ new(tdms_file.read_single)
109
+ end
110
+ end
111
+
112
+ class DoubleWithUnit < Base
113
+ Id = 0x1A
114
+ LengthInBytes = 8
115
+
116
+ def self.read_from_stream(tdms_file)
117
+ new(tdms_file.read_double)
118
+ end
119
+ end
120
+
121
+ class Utf8String < Base
122
+ Id = 0x20
123
+ LengthInBytes = nil
124
+
125
+ def self.read_from_stream(tdms_file)
126
+ new(tdms_file.read_utf8_string)
127
+ end
128
+ end
129
+
130
+ class Boolean < Base
131
+ Id = 0x21
132
+ LengthInBytes = 1
133
+
134
+ def self.read_from_stream(tdms_file)
135
+ new(tdms_file.read_bool)
136
+ end
137
+ end
138
+
139
+ class Timestamp < Base
140
+ Id = 0x44
141
+ LengthInBytes = 16
142
+
143
+ def self.read_from_stream(tdms_file)
144
+ new(tdms_file.read_timestamp)
145
+ end
146
+ end
147
+
148
+ DataTypesById = {
149
+ Int8::Id => Int8,
150
+ Int16::Id => Int16,
151
+ Int32::Id => Int32,
152
+ Int64::Id => Int64,
153
+ Uint8::Id => Uint8,
154
+ Uint16::Id => Uint16,
155
+ Uint32::Id => Uint32,
156
+ Uint64::Id => Uint64,
157
+ Single::Id => Single,
158
+ SingleWithUnit::Id => SingleWithUnit,
159
+ Double::Id => Double,
160
+ DoubleWithUnit::Id => DoubleWithUnit,
161
+ Utf8String::Id => Utf8String,
162
+ Boolean::Id => Boolean,
163
+ Timestamp::Id => Timestamp
164
+ }
165
+
166
+ def find_by_id(id_byte)
167
+ DataTypesById[id_byte] || raise(ArgumentError, "Don't know type %d" % id_byte)
168
+ end
169
+ module_function :find_by_id
170
+
171
+ end
172
+
173
+ end
@@ -0,0 +1,114 @@
1
+ module Tdms
2
+
3
+ class Document
4
+ attr_reader :segments, :channels, :file
5
+
6
+ def initialize(file)
7
+ @file = file
8
+ parse_segments
9
+ build_aggregates
10
+ end
11
+
12
+ private
13
+
14
+ def parse_segments
15
+ @segments = []
16
+
17
+ until file.eof?
18
+ segment = Tdms::Segment.new
19
+ segment.prev_segment = @segments[-1]
20
+ @segments << segment
21
+
22
+ lead_in = @file.read(0x1C)
23
+ metadata_pos = @file.pos
24
+
25
+ unpacked = lead_in.unpack("a4VVQQ")
26
+ tdms_tag = unpacked[0] # char[4]
27
+ toc_flags = unpacked[1] # u32
28
+ tdms_version = unpacked[2] # u32
29
+ next_seg_pos = unpacked[3] + metadata_pos # u64
30
+ raw_data_pos = unpacked[4] + metadata_pos # u64
31
+
32
+ new_changed_objs = @file.read_u32
33
+
34
+ raw_data_pos_obj = raw_data_pos
35
+
36
+ 1.upto(new_changed_objs) do |obj_index|
37
+ path = Tdms::Path.new(:path => @file.read_utf8_string)
38
+ index_block_len = @file.read_u32
39
+
40
+ if index_block_len == 0xFFFFFFFF
41
+ # no index block
42
+
43
+ elsif index_block_len == 0x000000
44
+ # index block is same as this channel in the last segment
45
+ prev_chan = segment.prev_segment.objects.find {|o| o.path == path }
46
+
47
+ chan = Tdms::Channel.new
48
+ chan.file = @file
49
+ chan.raw_data_pos = raw_data_pos_obj
50
+ chan.path = prev_chan.path
51
+ chan.data_type_id = prev_chan.data_type_id
52
+ chan.dimension = prev_chan.dimension
53
+ chan.num_values = prev_chan.num_values
54
+
55
+ segment.objects << chan
56
+ else
57
+ # XXX why does the number of properties seem to be
58
+ # included in the raw data index block size?
59
+ # -4 is a hack
60
+ index_block = @file.read(index_block_len - 4)
61
+ decoded = index_block.unpack("VVQ")
62
+
63
+ chan = Tdms::Channel.new
64
+ chan.file = @file
65
+ chan.raw_data_pos = raw_data_pos_obj
66
+ chan.path = path
67
+ chan.data_type_id = decoded[0] # first 4 bytes u32
68
+ chan.dimension = decoded[1] # next 4 bytes u32
69
+ chan.num_values = decoded[2] # next 8 bytes u64
70
+
71
+ data_type = Tdms::DataType.find_by_id(chan.data_type_id)
72
+ fixed_length = data_type::LengthInBytes
73
+
74
+ raw_data_pos_obj += if fixed_length
75
+ chan.num_values * fixed_length
76
+ else
77
+ # if the values are variable length (strings only) then
78
+ # the index block contains 8 additional bytes at the
79
+ # end with the total length of the raw data in u64
80
+ index_block[-8,8].unpack("Q")[0]
81
+ end
82
+
83
+ segment.objects << chan
84
+ end
85
+
86
+ # TODO store properties
87
+ num_props = @file.read_u32
88
+ 1.upto(num_props) do |n|
89
+ prop = @file.read_property
90
+ end
91
+ end
92
+
93
+ @file.seek next_seg_pos
94
+ end
95
+
96
+ end
97
+
98
+ def build_aggregates
99
+ @channels = []
100
+
101
+ channels_by_path = {}
102
+ segments.each do |segment|
103
+ segment.objects.select { |o| o.path.channel? }.each do |ch|
104
+ (channels_by_path[ch.path.to_s] ||= []) << ch
105
+ end
106
+ end
107
+
108
+ channels_by_path.each do |path, channels|
109
+ @channels << AggregateChannel.new(channels)
110
+ end
111
+ end
112
+ end
113
+
114
+ end
@@ -0,0 +1,58 @@
1
+ module Tdms
2
+
3
+ class Path
4
+ attr_reader :group, :channel
5
+
6
+ def initialize(options={})
7
+ load(options[:path]) if options[:path]
8
+ @group = options[:group] if options[:group]
9
+ @channel = options[:channel] if options[:channel]
10
+ end
11
+
12
+ def load(path)
13
+ segments = path.split("/").map do |seg|
14
+ seg.sub(/^'/,'').sub(/'$/,'').sub("''", "'")
15
+ end
16
+
17
+ _, @group, @channel = *segments
18
+ end
19
+
20
+ def ==(other)
21
+ if other.is_a?(String)
22
+ self.to_s == other
23
+ elsif other.is_a?(Path)
24
+ self.dump == other.dump
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def dump
31
+ raise ArgumentError if channel && group.nil?
32
+
33
+ parts = [""]
34
+ parts << ("'" + group.sub("'","''") + "'") if group
35
+ parts << ("'" + channel.sub("'","''") + "'") if channel
36
+
37
+ dumped = parts.join("/")
38
+ dumped.empty? ? "/" : dumped
39
+ end
40
+
41
+ def to_s
42
+ dump
43
+ end
44
+
45
+ def root?
46
+ (! channel?) && (! group?)
47
+ end
48
+
49
+ def group?
50
+ (! @group.nil?) && (@channel.nil?)
51
+ end
52
+
53
+ def channel?
54
+ (! @channel.nil?)
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,17 @@
1
+ module Tdms
2
+
3
+ class Property
4
+ attr_accessor :name
5
+ attr_accessor :data # tdms::data_type
6
+
7
+ def initialize(name=nil, data=nil)
8
+ @name = name
9
+ @data = data
10
+ end
11
+
12
+ def value
13
+ data.value
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Tdms
2
+
3
+ class Segment
4
+ attr_accessor :prev_segment
5
+ attr_reader :objects
6
+
7
+ def initialize
8
+ @objects = []
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,82 @@
1
+ require 'date'
2
+
3
+ module Tdms
4
+
5
+ module Streaming
6
+ def read_property
7
+ name = read_utf8_string
8
+ type_id = read_u32
9
+
10
+ data = Tdms::DataType.find_by_id(type_id).read_from_stream(self)
11
+ Tdms::Property.new(name, data)
12
+ end
13
+
14
+ def read_bool
15
+ read(1) == "\001"
16
+ end
17
+
18
+ def read_u8
19
+ read(1).unpack("C")[0]
20
+ end
21
+
22
+ def read_u16
23
+ read(2).unpack("v")[0]
24
+ end
25
+
26
+ def read_u32
27
+ read(4).unpack("V")[0]
28
+ end
29
+
30
+ def read_u64
31
+ lo_hi = read(8).unpack("VV")
32
+ lo_hi[0] + (lo_hi[1] << 32)
33
+ end
34
+
35
+ def read_i8
36
+ read(2).unpack("c")[0]
37
+ end
38
+
39
+ def read_i16
40
+ read(2).unpack("s")[0] # TODO little endian not native
41
+ end
42
+
43
+ def read_i32
44
+ read(4).unpack("l")[0] # TODO little endian not native
45
+ end
46
+
47
+ def read_i64
48
+ read(8).unpack("q")[0] # TODO little endian not native
49
+ end
50
+
51
+ def read_single
52
+ read(4).unpack("e")[0]
53
+ end
54
+
55
+ def read_double
56
+ read(8).unpack("E")[0]
57
+ end
58
+
59
+ def read_utf8_string
60
+ length = read_u32
61
+ read length
62
+ end
63
+
64
+ def read_timestamp
65
+ positive_fractions_of_second = read_u64 # ignored
66
+ seconds_since_labview_epoch = read(8).unpack("q")[0] # TODO little endian not native
67
+
68
+ labview_epoch = ::DateTime.new(1904, 1, 1)
69
+ labview_epoch + Rational(seconds_since_labview_epoch, 86400)
70
+ end
71
+ end
72
+
73
+ class File < ::File
74
+ include Streaming
75
+
76
+ def self.parse(filename)
77
+ f = self.open(filename, "rb")
78
+ Document.new(f)
79
+ end
80
+ end
81
+
82
+ end