fit4ruby 3.7.0 → 3.10.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 +4 -4
- data/README.md +1 -1
- data/fit4ruby.gemspec +2 -2
- data/lib/fit4ruby/Activity.rb +118 -55
- data/lib/fit4ruby/BDFieldNameTranslator.rb +36 -0
- data/lib/fit4ruby/FDR_DevField_Extension.rb +81 -0
- data/lib/fit4ruby/FieldDescription.rb +22 -10
- data/lib/fit4ruby/FileId.rb +2 -1
- data/lib/fit4ruby/FitDataRecord.rb +84 -31
- data/lib/fit4ruby/FitDefinition.rb +11 -4
- data/lib/fit4ruby/FitDefinitionField.rb +4 -4
- data/lib/fit4ruby/FitDefinitionFieldBase.rb +15 -6
- data/lib/fit4ruby/FitDeveloperDataFieldDefinition.rb +1 -1
- data/lib/fit4ruby/FitFile.rb +2 -0
- data/lib/fit4ruby/FitMessageRecord.rb +23 -18
- data/lib/fit4ruby/FitRecord.rb +2 -1
- data/lib/fit4ruby/FitTypeDefs.rb +2 -2
- data/lib/fit4ruby/GlobalFitDictionaries.rb +197 -2
- data/lib/fit4ruby/GlobalFitMessage.rb +79 -14
- data/lib/fit4ruby/GlobalFitMessages.rb +52 -6
- data/lib/fit4ruby/Lap.rb +10 -1
- data/lib/fit4ruby/Record.rb +11 -2
- data/lib/fit4ruby/Workout.rb +31 -0
- data/lib/fit4ruby/WorkoutStep.rb +32 -0
- data/lib/fit4ruby/version.rb +1 -1
- data/spec/FitFile_spec.rb +198 -100
- metadata +16 -13
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FitDataRecord.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015, 2020, 2021 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -12,19 +12,21 @@
|
|
12
12
|
|
13
13
|
require 'fit4ruby/FitMessageIdMapper'
|
14
14
|
require 'fit4ruby/GlobalFitMessages.rb'
|
15
|
+
require 'fit4ruby/BDFieldNameTranslator'
|
15
16
|
|
16
17
|
module Fit4Ruby
|
17
18
|
|
18
19
|
class FitDataRecord
|
19
20
|
|
20
21
|
include Converters
|
22
|
+
include BDFieldNameTranslator
|
21
23
|
|
22
|
-
RecordOrder = [ 'user_data', 'user_profile',
|
24
|
+
RecordOrder = [ 'user_data', 'user_profile', 'workout', 'workout_set',
|
23
25
|
'device_info', 'data_sources', 'event',
|
24
26
|
'record', 'lap', 'length', 'session', 'heart_rate_zones',
|
25
27
|
'personal_records' ]
|
26
28
|
|
27
|
-
attr_reader :message
|
29
|
+
attr_reader :message, :timestamp
|
28
30
|
|
29
31
|
def initialize(record_id)
|
30
32
|
@message = GlobalFitMessages.find_by_name(record_id)
|
@@ -34,6 +36,7 @@ module Fit4Ruby
|
|
34
36
|
@message.fields_by_name.each do |name, field|
|
35
37
|
create_instance_variable(name)
|
36
38
|
end
|
39
|
+
|
37
40
|
# Meta fields are additional fields that are not part of the FIT
|
38
41
|
# specification but are convenient to have. These are typcially
|
39
42
|
# aggregated or converted values of regular fields.
|
@@ -58,6 +61,7 @@ module Fit4Ruby
|
|
58
61
|
end
|
59
62
|
|
60
63
|
def get(name)
|
64
|
+
# This is a request for a native FIT field.
|
61
65
|
ivar_name = '@' + name
|
62
66
|
return nil unless instance_variable_defined?(ivar_name)
|
63
67
|
|
@@ -73,8 +77,7 @@ module Fit4Ruby
|
|
73
77
|
if @meta_field_units.include?(name)
|
74
78
|
unit = @meta_field_units[name]
|
75
79
|
else
|
76
|
-
|
77
|
-
unless (unit = field.opts[:unit])
|
80
|
+
unless (unit = get_unit_by_name(name))
|
78
81
|
Log.fatal "Field #{name} has no unit"
|
79
82
|
end
|
80
83
|
end
|
@@ -83,12 +86,13 @@ module Fit4Ruby
|
|
83
86
|
end
|
84
87
|
|
85
88
|
def ==(fdr)
|
86
|
-
@message.
|
87
|
-
ivar_name = '@' + name
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
89
|
+
@message.each_field(field_values_as_hash) do |number, field|
|
90
|
+
ivar_name = '@' + field.name
|
91
|
+
# Comparison of values is done in the fit file format as the accuracy
|
92
|
+
# of native formats is better and could lead to wrong results if a
|
93
|
+
# value hasn't been read back from a fit file yet.
|
94
|
+
v1 = field.native_to_fit(instance_variable_get(ivar_name))
|
95
|
+
v2 = field.native_to_fit(fdr.instance_variable_get(ivar_name))
|
92
96
|
|
93
97
|
return false unless v1 == v2
|
94
98
|
end
|
@@ -104,13 +108,8 @@ module Fit4Ruby
|
|
104
108
|
end
|
105
109
|
|
106
110
|
def write(io, id_mapper)
|
107
|
-
|
108
|
-
|
109
|
-
fields = {}
|
110
|
-
@message.fields_by_name.each_key do |name|
|
111
|
-
fields[name] = instance_variable_get('@' + name)
|
112
|
-
end
|
113
|
-
global_fit_message = @message.construct(fields)
|
111
|
+
global_fit_message = @message.construct(
|
112
|
+
field_values = field_values_as_hash)
|
114
113
|
|
115
114
|
# Map the global message number to the current local message number.
|
116
115
|
unless (local_message_number = id_mapper.get_local(global_fit_message))
|
@@ -131,42 +130,96 @@ module Fit4Ruby
|
|
131
130
|
|
132
131
|
# Create a BinData::Struct object to store the data record.
|
133
132
|
fields = []
|
134
|
-
|
135
|
-
bin_data_type = FitDefinitionFieldBase.fit_type_to_bin_data(field.type)
|
136
|
-
fields << [ bin_data_type, field.name ]
|
137
|
-
end
|
138
|
-
bd = BinData::Struct.new(:endian => :little, :fields => fields)
|
133
|
+
values = {}
|
139
134
|
|
140
135
|
# Fill the BinData::Struct object with the values from the corresponding
|
141
136
|
# instance variables.
|
142
|
-
global_fit_message.
|
137
|
+
global_fit_message.each_field(field_values) do |number, field|
|
138
|
+
bin_data_type = FitDefinitionFieldBase.fit_type_to_bin_data(field.type)
|
139
|
+
field_name = to_bd_field_name(field.name)
|
140
|
+
field_def = [ bin_data_type, field_name ]
|
141
|
+
|
143
142
|
iv = "@#{field.name}"
|
144
143
|
if instance_variable_defined?(iv) &&
|
145
144
|
!(iv_value = instance_variable_get(iv)).nil?
|
146
|
-
|
145
|
+
values[field.name] = field.native_to_fit(iv_value)
|
147
146
|
else
|
148
147
|
# If we don't have a corresponding variable or the variable is nil
|
149
148
|
# we write the 'undefined' value instead.
|
150
149
|
value = FitDefinitionFieldBase.undefined_value(field.type)
|
150
|
+
values[field.name] = field.opts[:array] ? [ value ] :
|
151
|
+
field.type == 'string' ? '' : value
|
151
152
|
end
|
152
|
-
|
153
|
+
|
154
|
+
# Some field types need special handling.
|
155
|
+
if field.type == 'string'
|
156
|
+
# Zero terminate the string.
|
157
|
+
values[field.name] += "\0"
|
158
|
+
elsif field.opts[:array]
|
159
|
+
# For Arrays we use a BinData::Array to write them.
|
160
|
+
field_def = [ :array, field_name,
|
161
|
+
{ :type => bin_data_type,
|
162
|
+
:initial_length => values[field.name].size } ]
|
163
|
+
end
|
164
|
+
fields << field_def
|
165
|
+
end
|
166
|
+
bd = BinData::Struct.new(:endian => :little, :fields => fields)
|
167
|
+
|
168
|
+
# Fill the BinData::Struct object with the values from the corresponding
|
169
|
+
# instance variables.
|
170
|
+
global_fit_message.each_field(field_values) do |number, field|
|
171
|
+
bd[to_bd_field_name(field.name)] = values[field.name]
|
153
172
|
end
|
154
173
|
|
155
174
|
# Write the data record to the file.
|
156
175
|
bd.write(io)
|
157
176
|
end
|
158
177
|
|
159
|
-
def
|
160
|
-
|
161
|
-
|
178
|
+
def export
|
179
|
+
message = {
|
180
|
+
'message' => @message.name,
|
181
|
+
'number' => @message.number,
|
182
|
+
'fields' => {}
|
183
|
+
}
|
184
|
+
|
185
|
+
@message.each_field(field_values_as_hash) do |number, field|
|
162
186
|
ivar_name = '@' + field.name
|
163
|
-
|
187
|
+
fit_value = field.native_to_fit(instance_variable_get(ivar_name))
|
188
|
+
unless field.is_undefined?(fit_value)
|
189
|
+
fld = {
|
190
|
+
'number' => number,
|
191
|
+
'value' => field.fit_to_native(fit_value),
|
192
|
+
#'human' => field.to_human(fit_value),
|
193
|
+
'type' => field.type
|
194
|
+
}
|
195
|
+
fld['unit'] = field.opts[:unit] if field.opts[:unit]
|
196
|
+
fld['scale'] = field.opts[:scale] if field.opts[:scale]
|
197
|
+
|
198
|
+
message['fields'][field.name] = fld
|
199
|
+
end
|
164
200
|
end
|
165
|
-
|
201
|
+
|
202
|
+
message
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_unit_by_name(name)
|
206
|
+
field = @message.fields_by_name[name]
|
207
|
+
field.opts[:unit]
|
166
208
|
end
|
167
209
|
|
168
210
|
private
|
169
211
|
|
212
|
+
def field_values_as_hash
|
213
|
+
# Construct a GlobalFitMessage object that matches exactly the provided
|
214
|
+
# set of fields. It does not contain any AltField objects.
|
215
|
+
field_values = {}
|
216
|
+
@message.fields_by_name.each_key do |name|
|
217
|
+
field_values[name] = instance_variable_get('@' + name)
|
218
|
+
end
|
219
|
+
|
220
|
+
field_values
|
221
|
+
end
|
222
|
+
|
170
223
|
def create_instance_variable(name)
|
171
224
|
# Create a new instance variable for 'name'. We initialize it with a
|
172
225
|
# provided default value or nil.
|
@@ -63,9 +63,7 @@ module Fit4Ruby
|
|
63
63
|
super(io)
|
64
64
|
end
|
65
65
|
|
66
|
-
def
|
67
|
-
# We don't support writing developer data fields yet.
|
68
|
-
@@has_developer_data = false
|
66
|
+
def write(io)
|
69
67
|
super(io)
|
70
68
|
end
|
71
69
|
|
@@ -77,11 +75,20 @@ module Fit4Ruby
|
|
77
75
|
@@fit_entity
|
78
76
|
end
|
79
77
|
|
80
|
-
def setup(fit_message_definition)
|
78
|
+
def setup(fit_message_definition, field_values_by_name)
|
81
79
|
fit_message_definition.fields_by_number.each do |number, f|
|
82
80
|
fdf = FitDefinitionField.new
|
83
81
|
fdf.field_definition_number = number
|
84
82
|
fdf.set_type(f.type)
|
83
|
+
value = field_values_by_name[f.name]
|
84
|
+
# For String and Array fields we must adjust the length in the
|
85
|
+
# definition message.
|
86
|
+
if value.is_a?(String) && f.is_string?
|
87
|
+
# String plus 0 byte
|
88
|
+
fdf.set_length(value.bytes.length + 1)
|
89
|
+
elsif value.is_a?(Array) && f.is_array?
|
90
|
+
fdf.set_length(value.length)
|
91
|
+
end
|
85
92
|
|
86
93
|
data_fields << fdf
|
87
94
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FitDefinitionField.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014, 2017, 2018 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2017, 2018, 2020 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -72,15 +72,15 @@ module Fit4Ruby
|
|
72
72
|
if value.kind_of?(Array)
|
73
73
|
ary = []
|
74
74
|
value.each { |v| ary << to_machine(v) }
|
75
|
-
ary
|
75
|
+
return ary
|
76
76
|
else
|
77
77
|
if @global_message_definition &&
|
78
78
|
(field = @global_message_definition.
|
79
79
|
fields_by_number[field_number]) &&
|
80
80
|
field.respond_to?('to_machine')
|
81
|
-
field.to_machine(value)
|
81
|
+
return field.to_machine(value)
|
82
82
|
else
|
83
|
-
value
|
83
|
+
return value
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FitDefinitionFieldBase.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014, 2017 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2017, 2020 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -30,7 +30,7 @@ module Fit4Ruby
|
|
30
30
|
|
31
31
|
def set_type(fit_type)
|
32
32
|
idx = FIT_TYPE_DEFS.index { |x| x[0] == fit_type }
|
33
|
-
raise "Unknown type #{fit_type}" unless idx
|
33
|
+
raise ArgumentError, "Unknown type #{fit_type}" unless idx
|
34
34
|
self.base_type_number = idx
|
35
35
|
self.byte_count = FIT_TYPE_DEFS[idx][3]
|
36
36
|
end
|
@@ -39,13 +39,22 @@ module Fit4Ruby
|
|
39
39
|
FIT_TYPE_DEFS[checked_base_type_number][fit_type ? 0 : 1]
|
40
40
|
end
|
41
41
|
|
42
|
+
def set_length(count)
|
43
|
+
if (byte_count = FIT_TYPE_DEFS[self.base_type_number][3] * count) > 255
|
44
|
+
raise ArgumentError,
|
45
|
+
"FitDefinitionField byte count too large (#{byte_count})"
|
46
|
+
end
|
47
|
+
self.byte_count = byte_count
|
48
|
+
end
|
49
|
+
|
42
50
|
def is_array?
|
43
51
|
if total_bytes > base_type_bytes
|
44
52
|
if total_bytes % base_type_bytes != 0
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
53
|
+
raise RuntimeError,
|
54
|
+
"Total bytes (#{total_bytes}) must be multiple of " +
|
55
|
+
"base type bytes (#{base_type_bytes}) of type " +
|
56
|
+
"#{base_type_number.snapshot} in Global FIT " +
|
57
|
+
"Message #{name}."
|
49
58
|
end
|
50
59
|
return true
|
51
60
|
end
|
@@ -48,7 +48,7 @@ module Fit4Ruby
|
|
48
48
|
def find_field_definition
|
49
49
|
return @field_definition if @field_definition
|
50
50
|
|
51
|
-
tlr = parent.parent.fit_entity
|
51
|
+
tlr = parent.parent.fit_entity
|
52
52
|
@field_definition = tlr.field_descriptions.find do |fd|
|
53
53
|
fd.field_definition_number == field_number.snapshot &&
|
54
54
|
fd.developer_data_index == developer_data_index.snapshot
|
data/lib/fit4ruby/FitFile.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
#
|
4
4
|
# = FitMessageRecord.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014, 2015, 2018, 2019
|
6
|
+
# Copyright (c) 2014, 2015, 2018, 2019, 2020
|
7
|
+
# by Chris Schlaeger <cs@taskjuggler.org>
|
7
8
|
#
|
8
9
|
# This program is free software; you can redistribute it and/or modify
|
9
10
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -15,6 +16,7 @@ require 'fit4ruby/Log'
|
|
15
16
|
require 'fit4ruby/GlobalFitMessage'
|
16
17
|
require 'fit4ruby/FitFileEntity'
|
17
18
|
require 'fit4ruby/DumpedField'
|
19
|
+
require 'fit4ruby/BDFieldNameTranslator'
|
18
20
|
|
19
21
|
module Fit4Ruby
|
20
22
|
|
@@ -26,6 +28,8 @@ module Fit4Ruby
|
|
26
28
|
# decendents are used.
|
27
29
|
class FitMessageRecord
|
28
30
|
|
31
|
+
include BDFieldNameTranslator
|
32
|
+
|
29
33
|
attr_reader :global_message_number, :name, :message_record
|
30
34
|
|
31
35
|
def initialize(definition)
|
@@ -44,15 +48,9 @@ module Fit4Ruby
|
|
44
48
|
def read(io, entity, filter = nil, fields_dump = nil, fit_entity)
|
45
49
|
@message_record.read(io)
|
46
50
|
|
47
|
-
# Check if we have a developer defined message for this global message
|
48
|
-
# number.
|
49
|
-
if fit_entity.top_level_record
|
50
|
-
developer_fields = fit_entity.top_level_record.field_descriptions
|
51
|
-
@dfm = developer_fields[@global_message_number]
|
52
|
-
end
|
53
|
-
|
54
51
|
if @name == 'file_id'
|
55
|
-
|
52
|
+
# Caveat: 'type' is used as '_type' in BinData fields!
|
53
|
+
unless (entity_type = @message_record['_type'].snapshot)
|
56
54
|
Log.fatal "Corrupted FIT file: file_id record has no type definition"
|
57
55
|
end
|
58
56
|
entity.set_type(entity_type)
|
@@ -70,14 +68,18 @@ module Fit4Ruby
|
|
70
68
|
f1alt ? 1 : -1
|
71
69
|
end
|
72
70
|
|
73
|
-
#(sorted_fields + @definition.developer_fields).each do |field|
|
74
71
|
sorted_fields.each do |field|
|
75
|
-
value = @message_record[field.name].snapshot
|
72
|
+
value = @message_record[to_bd_field_name(field.name)].snapshot
|
76
73
|
# Strings are null byte terminated. There may be more bytes in the
|
77
74
|
# file, but we have to discard all bytes from the first null byte
|
78
75
|
# onwards.
|
79
|
-
if value.is_a?(String)
|
80
|
-
|
76
|
+
if value.is_a?(String)
|
77
|
+
if (null_byte = value.index("\0"))
|
78
|
+
value = null_byte == 0 ? '' : value[0..(null_byte - 1)]
|
79
|
+
end
|
80
|
+
if value.empty?
|
81
|
+
value = nil
|
82
|
+
end
|
81
83
|
end
|
82
84
|
|
83
85
|
field_name, field_def = get_field_name_and_global_def(field, obj)
|
@@ -86,8 +88,8 @@ module Fit4Ruby
|
|
86
88
|
if filter && fields_dump &&
|
87
89
|
(filter.field_names.nil? ||
|
88
90
|
filter.field_names.include?(field_name)) &&
|
89
|
-
!(((value.
|
90
|
-
(value.count(field.undefined_value) == value.length)) ||
|
91
|
+
!(((value.is_a?(String) &&
|
92
|
+
(value.count(field.undefined_value.chr) == value.length)) ||
|
91
93
|
value == field.undefined_value) && filter.ignore_undef)
|
92
94
|
fields_dump << DumpedField.new(
|
93
95
|
@global_message_number,
|
@@ -109,7 +111,7 @@ module Fit4Ruby
|
|
109
111
|
next
|
110
112
|
end
|
111
113
|
|
112
|
-
field_name = field_description.
|
114
|
+
field_name = field_description.full_field_name(fit_entity)
|
113
115
|
units = field_description.units
|
114
116
|
type = field.type
|
115
117
|
native_message_number = field_description.native_mesg_num
|
@@ -118,6 +120,8 @@ module Fit4Ruby
|
|
118
120
|
value = @message_record[field.name].snapshot
|
119
121
|
value = nil if value == field.undefined_value
|
120
122
|
|
123
|
+
obj.set(field_name, value) if obj
|
124
|
+
|
121
125
|
if filter && fields_dump &&
|
122
126
|
(filter.field_names.nil? ||
|
123
127
|
filter.field_names.include?(field_name)) &&
|
@@ -178,7 +182,8 @@ module Fit4Ruby
|
|
178
182
|
fields = []
|
179
183
|
(definition.data_fields.to_a +
|
180
184
|
definition.developer_fields.to_a).each do |field|
|
181
|
-
|
185
|
+
field_name = to_bd_field_name(field.name)
|
186
|
+
field_def = [ field.type, field_name ]
|
182
187
|
|
183
188
|
# Some field types need special handling.
|
184
189
|
if field.type == 'string'
|
@@ -186,7 +191,7 @@ module Fit4Ruby
|
|
186
191
|
field_def << { :read_length => field.total_bytes }
|
187
192
|
elsif field.is_array?
|
188
193
|
# For Arrays we have to break them into separte fields.
|
189
|
-
field_def = [ :array,
|
194
|
+
field_def = [ :array, field_name,
|
190
195
|
{ :type => field.type.intern,
|
191
196
|
:initial_length =>
|
192
197
|
field.total_bytes / field.base_type_bytes } ]
|
data/lib/fit4ruby/FitRecord.rb
CHANGED
@@ -49,7 +49,8 @@ module Fit4Ruby
|
|
49
49
|
if header.normal? && header.message_type.snapshot == 1
|
50
50
|
# process definition message
|
51
51
|
definition = FitDefinition.read(
|
52
|
-
io, entity, header.developer_data_flag.snapshot,
|
52
|
+
io, entity, header.developer_data_flag.snapshot,
|
53
|
+
@fit_entity.top_level_record)
|
53
54
|
@definitions[local_message_type] = FitMessageRecord.new(definition)
|
54
55
|
else
|
55
56
|
# process data message
|
data/lib/fit4ruby/FitTypeDefs.rb
CHANGED
@@ -21,9 +21,9 @@ module Fit4Ruby
|
|
21
21
|
[ 'uint16', 'uint16', 0xFFFF, 2 ],
|
22
22
|
[ 'sint32', 'int32', 0x7FFFFFFF, 4 ],
|
23
23
|
[ 'uint32', 'uint32', 0xFFFFFFFF, 4 ],
|
24
|
-
[ 'string', 'string',
|
24
|
+
[ 'string', 'string', 0, 1 ],
|
25
25
|
[ 'float32', 'float', 0xFFFFFFFF, 4 ],
|
26
|
-
[ '
|
26
|
+
[ 'float64', 'double', 0xFFFFFFFFFFFFFFFF, 8 ],
|
27
27
|
[ 'uint8z', 'uint8', 0, 1 ],
|
28
28
|
[ 'uint16z', 'uint16', 0, 2 ],
|
29
29
|
[ 'uint32z', 'uint32', 0, 4 ],
|