ruby_tdms 2.0.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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +31 -0
- data/LICENSE.txt +26 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/demo.rb +14 -0
- data/doc/data_types.txt +23 -0
- data/doc/example_disasm.txt +47 -0
- data/doc/tdms_format.txt +101 -0
- data/doc/usage.txt +48 -0
- data/lib/ruby_tdms.rb +2 -0
- data/lib/ruby_tdms/aggregate_channel.rb +53 -0
- data/lib/ruby_tdms/aggregate_channel_enumerator.rb +50 -0
- data/lib/ruby_tdms/channel_enumerator.rb +33 -0
- data/lib/ruby_tdms/data_types.rb +22 -0
- data/lib/ruby_tdms/data_types/base.rb +19 -0
- data/lib/ruby_tdms/data_types/boolean.rb +15 -0
- data/lib/ruby_tdms/data_types/double.rb +19 -0
- data/lib/ruby_tdms/data_types/double_with_unit.rb +19 -0
- data/lib/ruby_tdms/data_types/int16.rb +19 -0
- data/lib/ruby_tdms/data_types/int32.rb +19 -0
- data/lib/ruby_tdms/data_types/int64.rb +19 -0
- data/lib/ruby_tdms/data_types/int8.rb +15 -0
- data/lib/ruby_tdms/data_types/single.rb +19 -0
- data/lib/ruby_tdms/data_types/single_with_unit.rb +19 -0
- data/lib/ruby_tdms/data_types/string.rb +15 -0
- data/lib/ruby_tdms/data_types/timestamp.rb +15 -0
- data/lib/ruby_tdms/data_types/u_int16.rb +19 -0
- data/lib/ruby_tdms/data_types/u_int32.rb +19 -0
- data/lib/ruby_tdms/data_types/u_int64.rb +19 -0
- data/lib/ruby_tdms/data_types/u_int8.rb +15 -0
- data/lib/ruby_tdms/document.rb +105 -0
- data/lib/ruby_tdms/file.rb +13 -0
- data/lib/ruby_tdms/object_parser.rb +47 -0
- data/lib/ruby_tdms/objects/base.rb +47 -0
- data/lib/ruby_tdms/objects/channel.rb +105 -0
- data/lib/ruby_tdms/objects/file.rb +11 -0
- data/lib/ruby_tdms/objects/group.rb +22 -0
- data/lib/ruby_tdms/path.rb +97 -0
- data/lib/ruby_tdms/property.rb +16 -0
- data/lib/ruby_tdms/segment.rb +107 -0
- data/lib/ruby_tdms/streaming.rb +124 -0
- data/lib/ruby_tdms/string_channel_enumerator.rb +49 -0
- data/lib/ruby_tdms/version.rb +3 -0
- data/ruby_tdms.gemspec +38 -0
- metadata +185 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module RubyTDMS
|
|
2
|
+
class ChannelEnumerator
|
|
3
|
+
include Enumerable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def initialize(channel)
|
|
7
|
+
@channel = channel
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def size
|
|
12
|
+
@size ||= @channel.value_count
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def each
|
|
17
|
+
0.upto(size - 1) { |i| yield self[i] }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def [](i)
|
|
22
|
+
if (i < 0) || (i >= size)
|
|
23
|
+
raise RangeError, 'Channel %s has a range of 0 to %d, got invalid index: %d' % [@channel.path, size - 1, i]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
chunk_index = i / @channel.chunk_value_count
|
|
27
|
+
|
|
28
|
+
offset = (@channel.raw_data_offset + (@channel.chunk_length * chunk_index)) + (i * @channel.value_offset)
|
|
29
|
+
@channel.stream.seek offset
|
|
30
|
+
@channel.data_type.read_from_stream(@channel.stream, @channel.segment.big_endian?).value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'require_all'
|
|
2
|
+
require_rel 'data_types'
|
|
3
|
+
|
|
4
|
+
module RubyTDMS
|
|
5
|
+
module DataTypes
|
|
6
|
+
class << self
|
|
7
|
+
def find_by_id(id)
|
|
8
|
+
TYPES_BY_ID[id] || raise(ArgumentError, "No matching type for ID #{id.inspect}")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def to_hash
|
|
13
|
+
Base.subclasses.reduce({}) do |result, klass|
|
|
14
|
+
result[klass::ID] = klass
|
|
15
|
+
result
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
TYPES_BY_ID = to_hash.freeze
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module RubyTDMS
|
|
2
|
+
module DataTypes
|
|
3
|
+
class Base
|
|
4
|
+
attr_accessor :value
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def initialize(value = nil)
|
|
8
|
+
@value = value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def subclasses
|
|
14
|
+
ObjectSpace.each_object(singleton_class).select { |klass| klass < self }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class Double < Base
|
|
6
|
+
ID = 0x0A
|
|
7
|
+
LENGTH_IN_BYTES = 8
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_double_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_double
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class DoubleWithUnit < Base
|
|
6
|
+
ID = 0x1A
|
|
7
|
+
LENGTH_IN_BYTES = 8
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_double_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_double
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class Int16 < Base
|
|
6
|
+
ID = 0x02
|
|
7
|
+
LENGTH_IN_BYTES = 2
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_i16_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_i16
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class Int32 < Base
|
|
6
|
+
ID = 0x03
|
|
7
|
+
LENGTH_IN_BYTES = 4
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_i32_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_i32
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class Int64 < Base
|
|
6
|
+
ID = 0x04
|
|
7
|
+
LENGTH_IN_BYTES = 8
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_i64_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_i64
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class Single < Base
|
|
6
|
+
ID = 0x09
|
|
7
|
+
LENGTH_IN_BYTES = 4
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_single_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_single
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class SingleWithUnit < Base
|
|
6
|
+
ID = 0x19
|
|
7
|
+
LENGTH_IN_BYTES = 4
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_single_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_single
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class UInt16 < Base
|
|
6
|
+
ID = 0x06
|
|
7
|
+
LENGTH_IN_BYTES = 2
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_u16_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_u16
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class UInt32 < Base
|
|
6
|
+
ID = 0x07
|
|
7
|
+
LENGTH_IN_BYTES = 4
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_u32_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_u32
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module RubyTDMS
|
|
4
|
+
module DataTypes
|
|
5
|
+
class UInt64 < Base
|
|
6
|
+
ID = 0x08
|
|
7
|
+
LENGTH_IN_BYTES = 8
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def self.read_from_stream(tdms_file, big_endian)
|
|
11
|
+
if big_endian
|
|
12
|
+
new tdms_file.read_u64_be
|
|
13
|
+
else
|
|
14
|
+
new tdms_file.read_u64
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'coalesce'
|
|
2
|
+
require_relative 'objects/channel'
|
|
3
|
+
require_relative 'segment'
|
|
4
|
+
|
|
5
|
+
module RubyTDMS
|
|
6
|
+
class Document
|
|
7
|
+
#attr_reader :segments, :channels, :file
|
|
8
|
+
attr_reader :segments, :stream
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def initialize(stream)
|
|
12
|
+
@channel_aggregates = []
|
|
13
|
+
@segments = []
|
|
14
|
+
@stream = stream
|
|
15
|
+
|
|
16
|
+
parse_segments
|
|
17
|
+
build_aggregates
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def channels
|
|
22
|
+
@channel_aggregates
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def groups
|
|
27
|
+
objects.select { |object| object.is_a? Objects::Group }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def objects
|
|
32
|
+
segments.flat_map { |segment| segment.objects }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# @return [Array<TDMS::Objects::Channel>] The un-aggregated channel objects in the current document.
|
|
37
|
+
def raw_channels
|
|
38
|
+
objects.select { |object| object.is_a? Objects::Channel }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Returns a hash representation of the entire TDMS document, looking vaguely like:
|
|
43
|
+
# {
|
|
44
|
+
# file : [
|
|
45
|
+
# properties: {}
|
|
46
|
+
# ],
|
|
47
|
+
# groups : [
|
|
48
|
+
# {
|
|
49
|
+
# path: '/Time Domain',
|
|
50
|
+
# properties: {}
|
|
51
|
+
#
|
|
52
|
+
# }
|
|
53
|
+
# ],
|
|
54
|
+
# channels : [
|
|
55
|
+
# {
|
|
56
|
+
# path: '/Time Domain/Current Phase A',
|
|
57
|
+
# name: 'Current Phase A',
|
|
58
|
+
# properties: { 'wf_start': 2015-05-23 05:22:22 },
|
|
59
|
+
# values: [
|
|
60
|
+
# 1,
|
|
61
|
+
# 2,
|
|
62
|
+
# 3,
|
|
63
|
+
# 4,
|
|
64
|
+
# 5
|
|
65
|
+
# ]
|
|
66
|
+
# }
|
|
67
|
+
# ]
|
|
68
|
+
# }
|
|
69
|
+
def as_json
|
|
70
|
+
{
|
|
71
|
+
file: objects.find { |object| object.is_a? Objects::File }.as_json,
|
|
72
|
+
groups: objects.select { |object| object.is_a? Objects::Group }.map(&:as_json),
|
|
73
|
+
channels: channels.map(&:as_json)
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
protected
|
|
79
|
+
|
|
80
|
+
def parse_segments
|
|
81
|
+
until stream.eof?
|
|
82
|
+
segment = Segment.parse_stream(stream, self)
|
|
83
|
+
break if segment.nil?
|
|
84
|
+
#@segments << segment
|
|
85
|
+
next_segment_offset = segment.meta_data_offset._?(segment.raw_data_offset) + segment.length
|
|
86
|
+
stream.seek next_segment_offset
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def build_aggregates
|
|
92
|
+
channels_by_path =
|
|
93
|
+
raw_channels.reduce({}) do |hash, channel|
|
|
94
|
+
hash[channel.path] ||= []
|
|
95
|
+
hash[channel.path] << channel
|
|
96
|
+
hash
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
channels_by_path.each_pair do |path, channels|
|
|
100
|
+
@channel_aggregates << AggregateChannel.new(channels)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|