gps_pvt 0.8.5 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,364 @@
1
+ # RTCM3 parser
2
+
3
+ require_relative 'util'
4
+
5
+ module GPS_PVT
6
+ class RTCM3
7
+ def initialize(io)
8
+ @io = io
9
+ @buf = []
10
+ end
11
+ def RTCM3.checksum(packet, range = 0..-4)
12
+ GPS_PVT::Util::CRC24Q::checksum(packet[range])
13
+ end
14
+ module Packet
15
+ def decode(bits_list, offset = nil)
16
+ Util::BitOp::extract(self, bits_list, offset || 24)
17
+ end
18
+ def message_number
19
+ decode([12]).first
20
+ end
21
+ DataFrame = proc{
22
+ unum_gen = proc{|n, sf|
23
+ next [n, proc{|v| v}] unless sf
24
+ [n, sf.kind_of?(Rational) ? proc{|v| (sf * v).to_f} : proc{|v| sf * v}]
25
+ }
26
+ num_gen = proc{|n, sf|
27
+ lim = 1 << (n - 1)
28
+ lim2 = lim << 1
29
+ next [n, proc{|v| v >= lim ? v - lim2 : v}] unless sf
30
+ [n, sf.kind_of?(Rational) ?
31
+ proc{|v| v -= lim2 if v >= lim; (sf * v).to_f} :
32
+ proc{|v| v -= lim2 if v >= lim; sf * v}]
33
+ }
34
+ num_sign_gen = proc{|n, sf|
35
+ lim = 1 << (n - 1)
36
+ next [n, proc{|v| v >= lim ? lim - v : v}] unless sf
37
+ [n, sf.kind_of?(Rational) ?
38
+ proc{|v| v = lim - v if v >= lim; (sf * v).to_f} :
39
+ proc{|v| v = lim - v if v >= lim; sf * v}]
40
+ }
41
+ invalidate = proc{|orig, err|
42
+ [orig[0], proc{|v| v == err ? nil : orig[1].call(v)}]
43
+ }
44
+ idx_list_gen = proc{|n, start|
45
+ start ||= 0
46
+ idx_list = (start...(start+n)).to_a.reverse
47
+ [n, proc{|v| idx_list.inject([]){|res, idx|
48
+ res.unshift(idx) if (v & 0x1) > 0
49
+ break res unless (v >>= 1) > 0
50
+ res
51
+ } }]
52
+ }
53
+ sc2rad = 3.1415926535898
54
+ df = { # {df_num => [bits, post_process] or generator_proc, ...}
55
+ 1 => proc{|n| n},
56
+ 2 => 12,
57
+ 3 => 12,
58
+ 4 => unum_gen.call(30, Rational(1, 1000)), # [sec]
59
+ 9 => 6,
60
+ 21 => 6,
61
+ 22 => 1,
62
+ 23 => 1,
63
+ 24 => 1,
64
+ 25 => num_gen.call(38, Rational(1, 10000)), # [m]
65
+ 34 => unum_gen.call(27, Rational(1, 1000)), # [sec]
66
+ 38 => 6,
67
+ 40 => 5,
68
+ 71 => 8,
69
+ 76 => 10,
70
+ 77 => proc{
71
+ idx2meter = [
72
+ 2.40, 3.40, 4.85, 6.85, 9.65, 13.65, 24.00, 48.00,
73
+ 96.00, 192.00, 384.00, 768.00, 1536.00, 3072.00, 6144.00]
74
+ [4, proc{|v| (v >= idx2meter.size) ? (idx2meter[-1] * 2) : idx2meter[v]}]
75
+ }.call, # [m]
76
+ 78 => 2,
77
+ 79 => num_gen.call(14, Rational(sc2rad, 1 << 43)), # [rad/s]
78
+ 81 => unum_gen.call(16, 1 << 4), # [sec]
79
+ 82 => num_gen.call(8, Rational(1, 1 << 55)), # [s/s^2]
80
+ 83 => num_gen.call(16, Rational(1, 1 << 43)), # [s/s]
81
+ 84 => num_gen.call(22, Rational(1, 1 << 31)), # [sec]
82
+ 85 => 10,
83
+ 86 => num_gen.call(16, Rational(1, 1 << 5)), # [m]
84
+ 87 => num_gen.call(16, Rational(sc2rad, 1 << 43)), # [rad/s]
85
+ 88 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad/s]
86
+ 89 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
87
+ 90 => unum_gen.call(32, Rational(1, 1 << 33)),
88
+ 91 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
89
+ 92 => unum_gen.call(32, Rational(1, 1 << 19)), # [m^1/2]
90
+ 93 => unum_gen.call(16, 1 << 4), # [sec]
91
+ 94 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
92
+ 95 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad/s]
93
+ 96 => num_gen.call(16, Rational(1, 1 << 29)), # [rad]
94
+ 97 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad/s]
95
+ 98 => num_gen.call(16, Rational(1, 1 << 5)), # [m]
96
+ 99 => num_gen.call(32, Rational(sc2rad, 1 << 31)), # [rad]
97
+ 100 => num_gen.call(24, Rational(sc2rad, 1 << 43)), # [rad/s]
98
+ 101 => num_gen.call(8, Rational(1, 1 << 31)), # [sec]
99
+ 102 => 6,
100
+ 103 => 1,
101
+ 104 => 1,
102
+ 105 => 1,
103
+ 106 => [2, proc{|v| [0, 30, 45, 60][v] * 60}], # [s]
104
+ 107 => [12, proc{|v|
105
+ hh, mm, ss = [v >> 7, (v & 0x7E) >> 1, (v & 0x1) > 0 ? 30 : 0]
106
+ hh * 3600 + mm * 60 + ss # [sec]
107
+ }],
108
+ 108 => 1,
109
+ 109 => 1,
110
+ 110 => unum_gen.call(7, 15 * 60), # [sec]
111
+ 111 => num_sign_gen.call(24, Rational(1000, 1 << 20)), # [m/s]
112
+ 112 => num_sign_gen.call(27, Rational(1000, 1 << 11)), # [m]
113
+ 113 => num_sign_gen.call(5, Rational(1000, 1 << 30)), # [m/s^2]
114
+ 120 => 1,
115
+ 121 => num_sign_gen.call(11, Rational(1, 1 << 40)),
116
+ 122 => 2, # (M)
117
+ 123 => 1, # (M)
118
+ 124 => num_sign_gen.call(22, Rational(1, 1 << 30)), # [sec]
119
+ 125 => num_sign_gen.call(5, Rational(1, 1 << 30)), # [sec], (M)
120
+ 126 => 5, # [day]
121
+ 127 => 1, # (M)
122
+ 128 => [4, proc{|v|
123
+ [1, 2, 2.5, 4, 5, 7, 10, 12, 14, 16, 32, 64, 128, 256, 512, 1024][v]
124
+ }], # [m] (M)
125
+ 129 => 11, # [day]
126
+ 130 => 2, # 1 => GLONASS-M, (M) fields are active
127
+ 131 => 1,
128
+ 132 => 11, # [day]
129
+ 133 => num_sign_gen.call(32, Rational(1, 1 << 31)), # [sec]
130
+ 134 => 5, # [4year], (M)
131
+ 135 => num_sign_gen.call(22, Rational(1, 1 << 30)), # [sec], (M)
132
+ 136 => 1, # (M)
133
+ 137 => 1,
134
+ 141 => 1,
135
+ 142 => 1,
136
+ 248 => 30,
137
+ 364 => 2,
138
+ 393 => 1,
139
+ 394 => idx_list_gen.call(64, 1),
140
+ 395 => idx_list_gen.call(32, 1),
141
+ 396 => proc{|df394, df395|
142
+ x_list = df394.product(df395)
143
+ idx_list = idx_list_gen.call(x_list.size)[1]
144
+ [x_list.size, proc{|v| x_list.values_at(*idx_list.call(v))}]
145
+ },
146
+ 397 => invalidate.call(unum_gen.call(8, Rational(1, 1000)), 0xFF), # [sec]
147
+ 398 => unum_gen.call(10, Rational(1, 1000 << 10)), # [sec]
148
+ 399 => invalidate.call(num_gen.call(14), 0x2000), # [m/s]
149
+ 404 => invalidate.call(num_gen.call(15, Rational(1, 10000)), 0x4000), # [m/s]
150
+ 405 => invalidate.call(num_gen.call(20, Rational(1, 1000 << 29)), 0x80000), # [sec]
151
+ 406 => invalidate.call(num_gen.call(24, Rational(1, 1000 << 31)), 0x800000), # [sec]
152
+ 407 => 10,
153
+ 408 => unum_gen.call(10, Rational(1, 1 << 4)), # [dB-Hz]
154
+ 409 => 3,
155
+ 411 => 2,
156
+ 412 => 2,
157
+ 416 => 3,
158
+ 417 => 1,
159
+ 418 => 3,
160
+ 420 => 1,
161
+ 429 => 4,
162
+ :uint => proc{|n| n},
163
+ }
164
+ df[27] = df[26] = df[25]
165
+ df[117] = df[114] = df[111]
166
+ df[118] = df[115] = df[112]
167
+ df[119] = df[116] = df[113]
168
+ {430..433 => 81..84, 434 => 71, 435..449 => 86..100, 450 => 79, 451 => 78,
169
+ 452 => 76, 453 => 77, 454 => 102, 455 => 101, 456 => 85, 457 => 137}.each{|dst, src|
170
+ # QZSS ephemeris => GPS
171
+ src = (src.to_a rescue [src]).flatten
172
+ (dst.to_a rescue ([dst] * src.size)).flatten.zip(src).each{|i, j| df[i] = df[j]}
173
+ }
174
+ df.define_singleton_method(:generate_prop){|idx_list|
175
+ hash = Hash[*([:bits, :op].collect.with_index{|k, i|
176
+ [k, idx_list.collect{|idx, *args|
177
+ case prop = self[idx]
178
+ when Proc; prop = prop.call(*args)
179
+ end
180
+ [prop].flatten(1)[i]
181
+ }]
182
+ }.flatten(1))].merge({:df => idx_list})
183
+ hash[:bits_total] = hash[:bits].inject{|a, b| a + b}
184
+ hash
185
+ }
186
+ df
187
+ }.call
188
+ MessageType = Hash[*({
189
+ 1005 => [2, 3, 21, 22, 23, 24, 141, 25, 142, [1, 1], 26, 364, 27],
190
+ 1019 => [2, 9, (76..79).to_a, 71, (81..103).to_a, 137].flatten, # 488 bits @see Table 3.5-21
191
+ 1020 => [2, 38, 40, (104..136).to_a].flatten, # 360 bits @see Table 3.5-21
192
+ 1044 => [2, (429..457).to_a].flatten, # 485 bits
193
+ 1077 => [2, 3, 4, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits @see Table 3.5-78
194
+ 1087 => [2, 3, 416, 34, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits @see Table 3.5-93
195
+ 1097 => [2, 3, 248, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits @see Table 3.5-98
196
+ 1117 => [2, 3, 4, 393, 409, [1, 7], 411, 412, 417, 418, 394, 395], # 169 bits
197
+ }.collect{|mt, df_list| [mt, DataFrame.generate_prop(df_list)]}.flatten(1))]
198
+ module GPS_Ephemeris
199
+ KEY2IDX = {:svid => 1, :WN => 2, :URA => 3, :dot_i0 => 5, :iode => 6, :t_oc => 7,
200
+ :a_f2 => 8, :a_f1 => 9, :a_f0 => 10, :iodc => 11, :c_rs => 12, :delta_n => 13,
201
+ :M0 => 14, :c_uc => 15, :e => 16, :c_us => 17, :sqrt_A => 18, :t_oe => 19, :c_ic => 20,
202
+ :Omega0 => 21, :c_is => 22, :i0 => 23, :c_rc => 24, :omega => 25, :dot_Omega0 => 26,
203
+ :t_GD => 27, :SV_health => 28}
204
+ def params
205
+ # TODO WN is truncated to 0-1023
206
+ res = Hash[*(KEY2IDX.collect{|k, i| [k, self[i][0]]}.flatten(1))]
207
+ res[:fit_interval] = ((self[29] == 0) ? 4 : case res[:iodc]
208
+ when 240..247; 8
209
+ when 248..255, 496; 14
210
+ when 497..503; 26
211
+ when 504..510; 50
212
+ when 511, 752..756; 74
213
+ when 757..763; 98
214
+ when 764..767, 1088..1010; 122
215
+ when 1011..1020; 146
216
+ else; 6
217
+ end) * 60 * 60
218
+ res
219
+ end
220
+ end
221
+ module GLONASS_Ephemeris
222
+ def params
223
+ # TODO insufficient: :n => ?(String4); extra: :P3
224
+ # TODO generate time with t_b, N_T, NA, N_4
225
+ # TODO GPS.i is required to modify to generate EPhemeris_with_GPS_Time
226
+ k_i = {:svid => 1, :freq_ch => 2, :P1 => 5, :t_k => 6, :B_n => 7, :P2 => 8, :t_b => 9,
227
+ :xn_dot => 10, :xn => 11, :xn_ddot => 12,
228
+ :yn_dot => 13, :yn => 14, :yn_ddot => 15,
229
+ :zn_dot => 16, :zn => 17, :zn_ddot => 18,
230
+ :P3 => 19, :gamma_n => 20, :p => 21, :tau_n => 23, :delta_tau_n => 24, :E_n => 25,
231
+ :P4 => 26, :F_T => 27, :N_T => 28, :M => 29}
232
+ k_i.merge!({:NA => 31, :tau_c => 32, :N_4 => 33, :tau_GPS => 34}) if self[30][0] == 1 # check DF131
233
+ res = Hash[*(k_i.collect{|k, i| [k, self[i][0]]}.flatten(1))]
234
+ res.reject!{|k, v|
235
+ case k
236
+ when :N_T; v == 0
237
+ when :p, :delta_tau_n, :P4, :F_T, :N_4, :tau_GPS; true # TODO sometimes delta_tau_n is valid?
238
+ else; false
239
+ end
240
+ } if (res[:M] != 1) # check DF130
241
+ res
242
+ end
243
+ end
244
+ module QZSS_Ephemeris
245
+ KEY2IDX = {:svid => 1, :t_oc => 2, :a_f2 => 3, :a_f1 => 4, :a_f0 => 5,
246
+ :iode => 6, :c_rs => 7, :delta_n => 8, :M0 => 9, :c_uc => 10, :e => 11,
247
+ :c_us => 12, :sqrt_A => 13, :t_oe => 14, :c_ic => 15, :Omega0 => 16,
248
+ :c_is => 17, :i0 => 18, :c_rc => 19, :omega => 20, :dot_Omega0 => 21,
249
+ :dot_i0 => 22, :WN => 24, :URA => 25, :SV_health => 26,
250
+ :t_GD => 27, :iodc => 28}
251
+ def params
252
+ # TODO PRN = svid + 192, WN is truncated to 0-1023
253
+ res = Hash[*(KEY2IDX.collect{|k, i| [k, self[i][0]]}.flatten(1))]
254
+ res[:fit_interval] = (self[29] == 0) ? 2 * 60 * 60 : nil # TODO how to treat fit_interval > 2 hrs
255
+ res
256
+ end
257
+ end
258
+ module MSM_Header
259
+ def more_data?
260
+ self.find{|v| v[1] == 393}[0] == 1
261
+ end
262
+ end
263
+ module MSM7
264
+ SPEED_OF_LIGHT = 299_792_458
265
+ def ranges
266
+ idx_sat = self.find_index{|v| v[1] == 394}
267
+ sats = self[idx_sat][0]
268
+ nsat = sats.size
269
+ cells = self[idx_sat + 2][0] # DF396
270
+ ncell = cells.size
271
+ offset = idx_sat + 3
272
+ range_rough = self[offset, nsat] # DF397
273
+ range_rough2 = self[offset + (nsat * 2), nsat] # DF398
274
+ delta_rough = self[offset + (nsat * 3), nsat] # DF399
275
+ range_fine = self[offset + (nsat * 4), ncell] # DF405
276
+ phase_fine = self[offset + (nsat * 4) + (ncell * 1), ncell] # DF406
277
+ delta_fine = self[offset + (nsat * 4) + (ncell * 5), ncell] # DF404
278
+ Hash[*([:pseudo_range, :phase_range, :phase_range_rate, :sat_sig].zip(
279
+ cells.collect.with_index{|(sat, sig), i|
280
+ i2 = sats.find_index(sat)
281
+ rough_ms = (range_rough2[i2][0] + range_rough[i2][0]) rescue nil
282
+ [(((range_fine[i][0] + rough_ms) * SPEED_OF_LIGHT) rescue nil),
283
+ (((phase_fine[i][0] + rough_ms) * SPEED_OF_LIGHT) rescue nil),
284
+ ((delta_fine[i][0] + delta_rough[i2][0]) rescue nil)]
285
+ }.transpose + [cells]).flatten(1))]
286
+ end
287
+ end
288
+ def parse
289
+ msg_num = message_number
290
+ return nil unless (mt = MessageType[msg_num])
291
+ # return [[value, df], ...]
292
+ values, df_list, attributes = [[], [], []]
293
+ add_proc = proc{|target, offset|
294
+ values += decode(target[:bits], offset).zip(target[:op]).collect{|v, op|
295
+ op ? op.call(v) : v
296
+ }
297
+ df_list += target[:df]
298
+ }
299
+ add_proc.call(mt)
300
+ case msg_num
301
+ when 1019
302
+ attributes << GPS_Ephemeris
303
+ when 1020
304
+ attributes << GLONASS_Ephemeris
305
+ when 1044
306
+ attributes << QZSS_Ephemeris
307
+ when 1077, 1087, 1097, 1117
308
+ # 1077(GPS), 1087(GLONASS), 1097(GALILEO), 1117(QZSS)
309
+ attributes << MSM7
310
+ nsat, nsig = [-2, -1].collect{|i| values[i].size}
311
+ offset = 24 + mt[:bits_total]
312
+ df396 = DataFrame.generate_prop([[396, values[-2], values[-1]]])
313
+ add_proc.call(df396, offset)
314
+ ncell = values[-1].size
315
+ offset += df396[:bits_total]
316
+ msm7_sat = DataFrame.generate_prop(
317
+ ([[397, [:uint, 4], 398, 399]] * nsat).transpose.flatten(1))
318
+ add_proc.call(msm7_sat, offset)
319
+ offset += msm7_sat[:bits_total]
320
+ msm7_sig = DataFrame.generate_prop(
321
+ ([[405, 406, 407, 420, 408, 404]] * ncell).transpose.flatten(1))
322
+ add_proc.call(msm7_sig, offset)
323
+ end
324
+ attributes << MSM_Header if (1070..1229).include?(msg_num)
325
+ res = values.zip(df_list)
326
+ attributes.empty? ? res : res.extend(*attributes)
327
+ end
328
+ end
329
+ def read_packet
330
+ while !@io.eof?
331
+ if @buf.size < 6 then
332
+ @buf += @io.read(6 - @buf.size).unpack('C*')
333
+ return nil if @buf.size < 6
334
+ end
335
+
336
+ if @buf[0] != 0xD3 then
337
+ @buf.shift
338
+ next
339
+ elsif (@buf[1] & 0xFC) != 0x0 then
340
+ @buf = @buf[2..-1]
341
+ next
342
+ end
343
+
344
+ len = ((@buf[1] & 0x3) << 8) + @buf[2]
345
+ if @buf.size < len + 6 then
346
+ @buf += @io.read(len + 6 - @buf.size).unpack('C*')
347
+ return nil if @buf.size < len + 6
348
+ end
349
+
350
+ #p (((["%02X"] * 3) + ["%06X"]).join(', '))%[*(@buf[(len + 3)..(len + 5)]) + [RTCM3::checksum(@buf)]]
351
+ if "\0#{@buf[(len + 3)..(len + 5)].pack('C3')}".unpack('N')[0] != RTCM3::checksum(@buf) then
352
+ @buf = @buf[2..-1]
353
+ next
354
+ end
355
+
356
+ packet = @buf[0..(len + 5)]
357
+ @buf = @buf[(len + 6)..-1]
358
+
359
+ return packet.extend(Packet)
360
+ end
361
+ return nil
362
+ end
363
+ end
364
+ end
data/lib/gps_pvt/util.rb CHANGED
@@ -36,6 +36,17 @@ proc{
36
36
  require 'open-uri'
37
37
  require_relative 'ntrip'
38
38
 
39
+ class URI::Ntrip
40
+ def read_format(options = {})
41
+ pnt_list = self.read_source_table(options).mount_points
42
+ case pnt_list[self.mount_point][:format]
43
+ when /u-?b(?:lo)?x/i; :ubx
44
+ when /RTCM 3/i; :rtcm3
45
+ else; nil
46
+ end
47
+ end
48
+ end
49
+
39
50
  module GPS_PVT
40
51
  module Util
41
52
  class << self
@@ -79,5 +90,38 @@ module Util
79
90
  }
80
91
  end
81
92
  end
93
+ module CRC24Q
94
+ POLY = 0x1864CFB
95
+ TABLE = 0x100.times.collect{|i|
96
+ res = i << 16
97
+ 8.times{
98
+ res <<= 1
99
+ res ^= POLY if (res & 0x1000000) > 0
100
+ }
101
+ res
102
+ }
103
+ def CRC24Q.checksum(bytes)
104
+ bytes.inject(0){|crc, byte|
105
+ ((crc << 8) & 0xFFFF00) ^ TABLE[byte ^ ((crc >> 16) & 0xFF)]
106
+ }
107
+ end
108
+ end
109
+ module BitOp
110
+ MASK = (1..8).collect{|i| (1 << i) - 1}.reverse
111
+ def BitOp.extract(src_bytes, bits_list, offset = 0)
112
+ res = []
113
+ bits_list.inject(offset.divmod(8) + [offset]){|(qM, rM, skip), bits|
114
+ qL, rL = (skip += bits).divmod(8)
115
+ v = src_bytes[qM] & MASK[rM]
116
+ res << if rL > 0 then
117
+ src_bytes[(qM+1)..qL].inject(v){|v2, b| (v2 << 8) | b} >> (8 - rL)
118
+ else
119
+ src_bytes[(qM+1)..(qL-1)].inject(v){|v2, b| (v2 << 8) | b}
120
+ end
121
+ [qL, rL, skip]
122
+ }
123
+ res
124
+ end
125
+ end
82
126
  end
83
127
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GPS_PVT
4
- VERSION = "0.8.5"
4
+ VERSION = "0.9.0"
5
5
 
6
6
  def GPS_PVT.version_compare(a, b)
7
7
  Gem::Version::new(a) <=> Gem::Version::new(b)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gps_pvt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fenrir(M.Naruoka)
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-22 00:00:00.000000000 Z
11
+ date: 2023-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyserial
@@ -137,6 +137,8 @@ files:
137
137
  - lib/gps_pvt/ntrip.rb
138
138
  - lib/gps_pvt/receiver.rb
139
139
  - lib/gps_pvt/receiver/extension.rb
140
+ - lib/gps_pvt/receiver/rtcm3.rb
141
+ - lib/gps_pvt/rtcm3.rb
140
142
  - lib/gps_pvt/ubx.rb
141
143
  - lib/gps_pvt/util.rb
142
144
  - lib/gps_pvt/version.rb