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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +31 -0
  7. data/LICENSE.txt +26 -0
  8. data/README.md +28 -0
  9. data/Rakefile +10 -0
  10. data/demo.rb +14 -0
  11. data/doc/data_types.txt +23 -0
  12. data/doc/example_disasm.txt +47 -0
  13. data/doc/tdms_format.txt +101 -0
  14. data/doc/usage.txt +48 -0
  15. data/lib/ruby_tdms.rb +2 -0
  16. data/lib/ruby_tdms/aggregate_channel.rb +53 -0
  17. data/lib/ruby_tdms/aggregate_channel_enumerator.rb +50 -0
  18. data/lib/ruby_tdms/channel_enumerator.rb +33 -0
  19. data/lib/ruby_tdms/data_types.rb +22 -0
  20. data/lib/ruby_tdms/data_types/base.rb +19 -0
  21. data/lib/ruby_tdms/data_types/boolean.rb +15 -0
  22. data/lib/ruby_tdms/data_types/double.rb +19 -0
  23. data/lib/ruby_tdms/data_types/double_with_unit.rb +19 -0
  24. data/lib/ruby_tdms/data_types/int16.rb +19 -0
  25. data/lib/ruby_tdms/data_types/int32.rb +19 -0
  26. data/lib/ruby_tdms/data_types/int64.rb +19 -0
  27. data/lib/ruby_tdms/data_types/int8.rb +15 -0
  28. data/lib/ruby_tdms/data_types/single.rb +19 -0
  29. data/lib/ruby_tdms/data_types/single_with_unit.rb +19 -0
  30. data/lib/ruby_tdms/data_types/string.rb +15 -0
  31. data/lib/ruby_tdms/data_types/timestamp.rb +15 -0
  32. data/lib/ruby_tdms/data_types/u_int16.rb +19 -0
  33. data/lib/ruby_tdms/data_types/u_int32.rb +19 -0
  34. data/lib/ruby_tdms/data_types/u_int64.rb +19 -0
  35. data/lib/ruby_tdms/data_types/u_int8.rb +15 -0
  36. data/lib/ruby_tdms/document.rb +105 -0
  37. data/lib/ruby_tdms/file.rb +13 -0
  38. data/lib/ruby_tdms/object_parser.rb +47 -0
  39. data/lib/ruby_tdms/objects/base.rb +47 -0
  40. data/lib/ruby_tdms/objects/channel.rb +105 -0
  41. data/lib/ruby_tdms/objects/file.rb +11 -0
  42. data/lib/ruby_tdms/objects/group.rb +22 -0
  43. data/lib/ruby_tdms/path.rb +97 -0
  44. data/lib/ruby_tdms/property.rb +16 -0
  45. data/lib/ruby_tdms/segment.rb +107 -0
  46. data/lib/ruby_tdms/streaming.rb +124 -0
  47. data/lib/ruby_tdms/string_channel_enumerator.rb +49 -0
  48. data/lib/ruby_tdms/version.rb +3 -0
  49. data/ruby_tdms.gemspec +38 -0
  50. 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,15 @@
1
+ require_relative 'base'
2
+
3
+ module RubyTDMS
4
+ module DataTypes
5
+ class Boolean < Base
6
+ ID = 0x21
7
+ LENGTH_IN_BYTES = 1
8
+
9
+
10
+ def self.read_from_stream(tdms_file, big_endian)
11
+ new tdms_file.read_bool
12
+ end
13
+ end
14
+ end
15
+ 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,15 @@
1
+ require_relative 'base'
2
+
3
+ module RubyTDMS
4
+ module DataTypes
5
+ class Int8 < Base
6
+ ID = 0x01
7
+ LENGTH_IN_BYTES = 1
8
+
9
+
10
+ def self.read_from_stream(tdms_file, big_endian)
11
+ new tdms_file.read_i8
12
+ end
13
+ end
14
+ end
15
+ 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,15 @@
1
+ require_relative 'base'
2
+
3
+ module RubyTDMS
4
+ module DataTypes
5
+ class String < Base
6
+ ID = 0x20
7
+ LENGTH_IN_BYTES = nil
8
+
9
+
10
+ def self.read_from_stream(tdms_file, big_endian)
11
+ new tdms_file.read_utf8_string
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'base'
2
+
3
+ module RubyTDMS
4
+ module DataTypes
5
+ class Timestamp < Base
6
+ ID = 0x44
7
+ LENGTH_IN_BYTES = 16
8
+
9
+
10
+ def self.read_from_stream(tdms_file, big_endian)
11
+ new tdms_file.read_timestamp
12
+ end
13
+ end
14
+ end
15
+ 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,15 @@
1
+ require_relative 'base'
2
+
3
+ module RubyTDMS
4
+ module DataTypes
5
+ class UInt8 < Base
6
+ ID = 0x05
7
+ LENGTH_IN_BYTES = 1
8
+
9
+
10
+ def self.read_from_stream(tdms_file, big_endian)
11
+ new tdms_file.read_u8
12
+ end
13
+ end
14
+ end
15
+ 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