Module: RubyFit::Helpers

Included in:
MessageWriter, Type
Defined in:
lib/rubyfit/helpers.rb

Constant Summary collapse

GARMIN_TIME_OFFSET =

Garmin timestamps start at 12:00:00 01-01-1989, 20 years after the unix epoch

631065600
DEGREES_TO_SEMICIRCLES =
2**31 / 180.0

Instance Method Summary collapse

Instance Method Details

#bytes2hex(bytes) ⇒ Object

Generates strings of hex bytes (for debugging)



74
75
76
77
78
79
# File 'lib/rubyfit/helpers.rb', line 74

def bytes2hex(bytes)
  bytes
    .map{|b| "0x#{b.to_s(16).ljust(2, "0")}"}
    .each_slice(8)
    .map{ |s| s.join(", ") }
end

#bytes2num(bytes, byte_count, unsigned = true, big_endian = true) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/rubyfit/helpers.rb', line 45

def bytes2num(bytes, byte_count, unsigned = true, big_endian = true)
  directive = {
    1 => "C",
    2 => "S",
    4 => "L",
    8 => "Q"
  }[byte_count]
  raise "Unsupported byte count: #{byte_count}" unless directive
  directive << (big_endian ? ">" : "<") if byte_count > 1
  directive.downcase! unless unsigned
  bytes.pack("C*").unpack(directive).first
end

#bytes2str(bytes) ⇒ Object

Converts a byte array to a string. Omits the last character of the byte array from the result if it is 0



68
69
70
71
# File 'lib/rubyfit/helpers.rb', line 68

def bytes2str(bytes)
  bytes = bytes[0...-1] if bytes.last == 0
  bytes.pack("C*")
end

#deg2semicircles(degrees) ⇒ Object



90
91
92
# File 'lib/rubyfit/helpers.rb', line 90

def deg2semicircles(degrees)
  (degrees * DEGREES_TO_SEMICIRCLES).truncate
end

#fit2unix_timestamp(timestamp) ⇒ Object



85
86
87
# File 'lib/rubyfit/helpers.rb', line 85

def fit2unix_timestamp(timestamp)
  timestamp + GARMIN_TIME_OFFSET
end

#make_message_header(opts = {}) ⇒ Object



101
102
103
104
105
106
# File 'lib/rubyfit/helpers.rb', line 101

def make_message_header(opts = {})
  result = 0
  result |= (1 << 6) if opts[:definition]
  result |= (opts[:local_number] || 0) & 0xF
  result
end

#num2bytes(num, byte_count, big_endian = true) ⇒ Object

Converts a fixnum or bignum into a byte array, optionally truncating or right-filling with 0 to match a certain size

Raises:

  • (ArgumentError)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rubyfit/helpers.rb', line 9

def num2bytes(num, byte_count, big_endian = true)
  raise ArgumentError.new("num must be an integer") unless num.is_a?(Integer)
  orig_num = num
  # Convert negative numbers to two's complement (1-byte alignment)
  if num < 0
    num = num.abs

    if num > 2 ** (byte_count * 8 - 1)
      STDERR.puts("RubyFit WARNING: Integer underflow for #{orig_num} (#{orig_num.bit_length + 1} bits) when fitting in #{byte_count} bytes (#{byte_count * 8} bits)")
    end

    num = 2 ** (byte_count * 8) - num
  end

  hex = num.to_s(16)
  # pack('H*') assumes the high nybble is first, which reverses nybbles in
  # the most significant byte if it's only one hex char (<= 0xF). Prevent
  # this by prepending a zero if the hex string is an odd length
  hex = "0" + hex if hex.length.odd?
  result = [hex]
    .pack('H*')
    .unpack("C*")

  if result.size > byte_count
    STDERR.puts("RubyFit WARNING: Truncating #{orig_num} (#{orig_num.bit_length} bits) to fit in #{byte_count} bytes (#{byte_count * 8} bits)")
    result = result.last(byte_count)
  elsif result.size < byte_count
    pad_bytes = [0] * (byte_count - result.size)
    result.unshift(*pad_bytes)
  end

  result.reverse! unless big_endian

  result
end

#semicircles2deg(degrees) ⇒ Object



94
95
96
97
98
99
# File 'lib/rubyfit/helpers.rb', line 94

def semicircles2deg(degrees)
  result = degrees / DEGREES_TO_SEMICIRCLES
  result -= 360.0 if result > 180.0
  result += 360.0 if result < -180.0
  result
end

#str2bytes(str, byte_count) ⇒ Object

Converts an ASCII string into a byte array, truncating or right-filling with 0 to match byte_count



60
61
62
63
64
# File 'lib/rubyfit/helpers.rb', line 60

def str2bytes(str, byte_count)
  str
    .unpack("C#{byte_count - 1}") # Convert to n-1 bytes
    .map{|v| v || 0} + [0] # Convert nils to 0 and add null terminator
end

#unix2fit_timestamp(timestamp) ⇒ Object



81
82
83
# File 'lib/rubyfit/helpers.rb', line 81

def unix2fit_timestamp(timestamp)
  timestamp - GARMIN_TIME_OFFSET
end