1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.gnss.metric.ntrip;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InputStreamReader;
23 import java.net.Authenticator;
24 import java.net.HttpURLConnection;
25 import java.net.InetAddress;
26 import java.net.InetSocketAddress;
27 import java.net.Proxy;
28 import java.net.Proxy.Type;
29 import java.net.SocketAddress;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.net.URL;
33 import java.net.URLConnection;
34 import java.net.UnknownHostException;
35 import java.nio.charset.StandardCharsets;
36 import java.util.ArrayList;
37 import java.util.Formatter;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.concurrent.ExecutorService;
43 import java.util.concurrent.Executors;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.atomic.AtomicReference;
46
47 import org.hipparchus.util.FastMath;
48 import org.orekit.errors.OrekitException;
49 import org.orekit.errors.OrekitMessages;
50 import org.orekit.frames.Frame;
51 import org.orekit.gnss.metric.messages.ParsedMessage;
52 import org.orekit.time.TimeScales;
53
54
55
56
57
58
59
60
61
62
63 public class NtripClient {
64
65
66 public static final int DEFAULT_TIMEOUT = 10000;
67
68
69 public static final int DEFAULT_PORT = 2101;
70
71
72 public static final double DEFAULT_RECONNECT_DELAY = 1.0;
73
74
75 public static final double DEFAULT_RECONNECT_DELAY_FACTOR = 1.5;
76
77
78 public static final int DEFAULT_MAX_RECONNECT = 20;
79
80
81 private static final String HOST_HEADER_KEY = "Host";
82
83
84 private static final String USER_AGENT_HEADER_KEY = "User-Agent";
85
86
87 private static final String USER_AGENT_HEADER_VALUE = "NTRIP orekit/11.0";
88
89
90 private static final String VERSION_HEADER_KEY = "Ntrip-Version";
91
92
93 private static final String VERSION_HEADER_VALUE = "Ntrip/2.0";
94
95
96 private static final String CONNECTION_HEADER_KEY = "Connection";
97
98
99 private static final String CONNECTION_HEADER_VALUE = "close";
100
101
102 private static final String FLAGS_HEADER_KEY = "Ntrip-Flags";
103
104
105 private static final String SOURCETABLE_CONTENT_TYPE = "gnss/sourcetable";
106
107
108 private static final double DEG_TO_MINUTES = 60.0;
109
110
111 private final String host;
112
113
114 private final int port;
115
116
117 private double reconnectDelay;
118
119
120 private double reconnectDelayFactor;
121
122
123 private int maxRetries;
124
125
126 private int timeout;
127
128
129 private Proxy proxy;
130
131
132 private final AtomicReference<String> gga;
133
134
135 private final List<ObserverHolder> observers;
136
137
138 private final Map<String, StreamMonitor> monitors;
139
140
141 private SourceTable sourceTable;
142
143
144 private ExecutorService executorService;
145
146
147
148
149 private final TimeScales timeScales;
150
151
152
153
154 private final Frame inertial;
155
156
157
158
159 private final Frame bodyFixed;
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 public NtripClient(final String host, final int port, final TimeScales timeScales,
176 final int maxRetries, final Frame inertial, final Frame bodyFixed) {
177 this.host = host;
178 this.port = port;
179 this.observers = new ArrayList<>();
180 this.monitors = new HashMap<>();
181 setTimeout(DEFAULT_TIMEOUT);
182 setReconnectParameters(DEFAULT_RECONNECT_DELAY,
183 DEFAULT_RECONNECT_DELAY_FACTOR,
184 maxRetries);
185 setProxy(Type.DIRECT, null, -1);
186 this.gga = new AtomicReference<>(null);
187 this.sourceTable = null;
188 this.executorService = null;
189 this.timeScales = timeScales;
190 this.inertial = inertial;
191 this.bodyFixed = bodyFixed;
192 }
193
194
195
196
197 public String getHost() {
198 return host;
199 }
200
201
202
203
204 public int getPort() {
205 return port;
206 }
207
208
209
210
211
212 public TimeScales getTimeScales() {
213 return timeScales;
214 }
215
216
217
218
219 public void setTimeout(final int timeout) {
220 this.timeout = timeout;
221 }
222
223
224
225
226
227
228 public void setReconnectParameters(final double delay,
229 final double delayFactor,
230 final int max) {
231 this.reconnectDelay = delay;
232 this.reconnectDelayFactor = delayFactor;
233 this.maxRetries = max;
234 }
235
236
237
238
239
240
241 public void setProxy(final Proxy.Type type, final String proxyHost, final int proxyPort) {
242 try {
243 if (type == Proxy.Type.DIRECT) {
244
245 proxy = Proxy.NO_PROXY;
246 } else {
247
248 final InetAddress hostAddress = InetAddress.getByName(proxyHost);
249 final SocketAddress proxyAddress = new InetSocketAddress(hostAddress, proxyPort);
250 proxy = new Proxy(type, proxyAddress);
251 }
252 } catch (UnknownHostException uhe) {
253 throw new OrekitException(uhe, OrekitMessages.UNKNOWN_HOST, proxyHost);
254 }
255 }
256
257
258
259
260 public Proxy getProxy() {
261 return proxy;
262 }
263
264
265
266
267
268
269
270
271
272
273 public void setFix(final int hour, final int minute, final double second,
274 final double latitude, final double longitude, final double ellAltitude,
275 final double undulation) {
276
277
278 final double latDeg = FastMath.abs(FastMath.toDegrees(latitude));
279 final int dLat = (int) FastMath.floor(latDeg);
280 final double mLat = DEG_TO_MINUTES * (latDeg - dLat);
281 final char cLat = latitude >= 0.0 ? 'N' : 'S';
282
283
284 final double lonDeg = FastMath.abs(FastMath.toDegrees(longitude));
285 final int dLon = (int) FastMath.floor(lonDeg);
286 final double mLon = DEG_TO_MINUTES * (lonDeg - dLon);
287 final char cLon = longitude >= 0.0 ? 'E' : 'W';
288
289
290 final StringBuilder builder = new StringBuilder(82);
291 try (Formatter formatter = new Formatter(builder, Locale.US)) {
292
293
294 final int fixQuality = 1;
295 final int nbSat = 4;
296 final double hdop = 1.0;
297
298
299 formatter.format("$GPGGA,%02d%02d%06.3f,%02d%07.4f,%c,%02d%07.4f,%c,%1d,%02d,%3.1f,%.1f,M,%.1f,M,,",
300 hour, minute, second,
301 dLat, mLat, cLat, dLon, mLon, cLon,
302 fixQuality, nbSat, hdop,
303 ellAltitude, undulation);
304
305
306 byte sum = 0;
307 for (int i = 1; i < builder.length(); ++i) {
308 sum ^= builder.charAt(i);
309 }
310 formatter.format("*%02X", sum);
311
312 }
313 gga.set(builder.toString());
314
315 }
316
317
318
319
320 String getGGA() {
321 return gga.get();
322 }
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337 public void addObserver(final int typeCode, final String mountPoint,
338 final MessageObserver observer) {
339
340
341 observers.add(new ObserverHolder(typeCode, mountPoint, observer));
342
343
344 for (Map.Entry<String, StreamMonitor> entry : monitors.entrySet()) {
345 if (mountPoint == null || mountPoint.equals(entry.getKey())) {
346 entry.getValue().addObserver(typeCode, observer);
347 }
348 }
349
350 }
351
352
353
354
355 public SourceTable getSourceTable() {
356 if (sourceTable == null) {
357 try {
358
359
360 final HttpURLConnection connection = connect("");
361
362 final int responseCode = connection.getResponseCode();
363 if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
364 throw new OrekitException(OrekitMessages.FAILED_AUTHENTICATION, "caster");
365 } else if (responseCode != HttpURLConnection.HTTP_OK) {
366 throw new OrekitException(OrekitMessages.CONNECTION_ERROR, host, connection.getResponseMessage());
367 }
368
369
370 if (!SOURCETABLE_CONTENT_TYPE.equals(connection.getContentType())) {
371 throw new OrekitException(OrekitMessages.UNEXPECTED_CONTENT_TYPE, connection.getContentType());
372 }
373
374 final SourceTable table = new SourceTable(getHeaderValue(connection, FLAGS_HEADER_KEY));
375
376
377 try (InputStream is = connection.getInputStream();
378 InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
379 BufferedReader br = new BufferedReader(isr)) {
380 int lineNumber = 0;
381 for (String line = br.readLine(); line != null; line = br.readLine()) {
382
383 ++lineNumber;
384 line = line.trim();
385 if (line.isEmpty()) {
386 continue;
387 }
388
389 if (line.startsWith(RecordType.CAS.toString())) {
390 table.addCasterRecord(new CasterRecord(line));
391 } else if (line.startsWith(RecordType.NET.toString())) {
392 table.addNetworkRecord(new NetworkRecord(line));
393 } else if (line.startsWith(RecordType.STR.toString())) {
394 table.addDataStreamRecord(new DataStreamRecord(line));
395 } else if (line.startsWith("ENDSOURCETABLE")) {
396
397 break;
398 } else {
399 throw new OrekitException(OrekitMessages.SOURCETABLE_PARSE_ERROR,
400 connection.getURL().getHost(), lineNumber, line);
401 }
402
403 }
404 }
405
406 sourceTable = table;
407 return table;
408
409 } catch (IOException | URISyntaxException e) {
410 throw new OrekitException(e, OrekitMessages.CANNOT_PARSE_SOURCETABLE, host);
411 }
412 }
413
414 return sourceTable;
415
416 }
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435 public void startStreaming(final String mountPoint, final org.orekit.gnss.metric.ntrip.Type type,
436 final boolean requiresNMEA, final boolean ignoreUnknownMessageTypes) {
437
438 if (executorService == null) {
439
440 executorService = Executors.newFixedThreadPool(getSourceTable().getDataStreams().size());
441 }
442
443
444 if (monitors.containsKey(mountPoint)) {
445 throw new OrekitException(OrekitMessages.MOUNPOINT_ALREADY_CONNECTED, mountPoint);
446 }
447
448
449 final StreamMonitor monitor = new StreamMonitor(this, mountPoint, type, requiresNMEA, ignoreUnknownMessageTypes,
450 reconnectDelay, reconnectDelayFactor, maxRetries,
451 inertial, bodyFixed);
452 monitors.put(mountPoint, monitor);
453
454
455 for (final ObserverHolder observerHolder : observers) {
456 if (observerHolder.mountPoint == null ||
457 observerHolder.mountPoint.equals(mountPoint)) {
458 monitor.addObserver(observerHolder.typeCode, observerHolder.observer);
459 }
460 }
461
462
463 executorService.execute(monitor);
464
465 }
466
467
468
469
470
471
472 public void checkException() {
473
474 for (final Map.Entry<String, StreamMonitor> entry : monitors.entrySet()) {
475 final OrekitException exception = entry.getValue().getException();
476 if (exception != null) {
477 throw exception;
478 }
479 }
480 }
481
482
483
484
485
486
487
488 public void stopStreaming(final int time) {
489
490
491 for (final Map.Entry<String, StreamMonitor> entry : monitors.entrySet()) {
492 entry.getValue().stopMonitoring();
493 }
494
495 try {
496
497 executorService.shutdown();
498 executorService.awaitTermination(time, TimeUnit.MILLISECONDS);
499 } catch (InterruptedException ie) {
500
501 Thread.currentThread().interrupt();
502 }
503
504 checkException();
505
506 }
507
508
509
510
511
512
513
514 HttpURLConnection connect(final String mountPoint)
515 throws IOException, URISyntaxException {
516
517
518 final String scheme = "http";
519 final URL casterURL = new URI(scheme, null, host, port, "/" + mountPoint, null, null).toURL();
520 final HttpURLConnection connection = (HttpURLConnection) casterURL.openConnection(proxy);
521 connection.setConnectTimeout(timeout);
522 connection.setReadTimeout(timeout);
523
524
525 connection.setRequestProperty(HOST_HEADER_KEY, host);
526 connection.setRequestProperty(VERSION_HEADER_KEY, VERSION_HEADER_VALUE);
527 connection.setRequestProperty(USER_AGENT_HEADER_KEY, USER_AGENT_HEADER_VALUE);
528 connection.setRequestProperty(CONNECTION_HEADER_KEY, CONNECTION_HEADER_VALUE);
529
530 return connection;
531
532 }
533
534
535
536
537
538
539 private String getHeaderValue(final URLConnection connection, final String key) {
540 final String value = connection.getHeaderField(key);
541 if (value == null) {
542 throw new OrekitException(OrekitMessages.MISSING_HEADER,
543 connection.getURL().getHost(), key);
544 }
545 return value;
546 }
547
548
549 private static class ObserverHolder {
550
551
552 private final int typeCode;
553
554
555 private final String mountPoint;
556
557
558 private final MessageObserver observer;
559
560
561
562
563
564
565
566 ObserverHolder(final int typeCode, final String mountPoint,
567 final MessageObserver observer) {
568 this.typeCode = typeCode;
569 this.mountPoint = mountPoint;
570 this.observer = observer;
571 }
572
573 }
574
575 }