SubFrame.java
/* Copyright 2023 Thales Alenia Space
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.gnss.rflink.gps;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.gnss.metric.parser.EncodedMessage;
/**
* Container for sub-frames in a GPS navigation message.
* @author Luc Maisonobe
* @since 12.0
*/
public abstract class SubFrame {
/** TLM preamble. */
public static final int PREAMBLE_VALUE = 0x8b;
/** Words size. */
protected static final int WORD_SIZE = 30;
/** Size of parity field. */
protected static final int PARITY_SIZE = 6;
/** TLM preamble index. */
private static final int PREAMBLE = 0;
/** Telemetry message index. */
private static final int MESSAGE = 1;
/** Integrity status flag index. */
private static final int INTEGRITY_STATUS = 2;
/** Truncated Time Of Week count index. */
private static final int TOW_COUNT = 3;
/** Alert flag index. */
private static final int ALERT = 4;
/** Anti-spoofing flag index. */
private static final int ANTI_SPOOFING = 5;
/** Sub-frame ID index. */
private static final int ID = 6;
/** Raw data fields. */
private final int[] fields;
/** Simple constructor.
* @param words raw words
* @param nbFields number of fields in the sub-frame
* (including TLM and HOW data fields, excluding non-information and parity)
*/
protected SubFrame(final int[] words, final int nbFields) {
this.fields = new int[nbFields];
// common fields present in telemetry and handover words for all sub-frames
setField(PREAMBLE, 1, 22, 8, words);
setField(MESSAGE, 1, 8, 14, words);
setField(INTEGRITY_STATUS, 1, 7, 1, words);
setField(TOW_COUNT, 2, 13, 17, words);
setField(ALERT, 2, 12, 1, words);
setField(ANTI_SPOOFING, 2, 11, 1, words);
setField(ID, 2, 8, 3, words);
if (getField(PREAMBLE) != PREAMBLE_VALUE) {
throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, getField(PREAMBLE));
}
}
/** Builder for sub-frames.
* <p>
* This builder creates the proper sub-frame type corresponding to the ID in handover word
* and the SV Id for sub-frames 4 and 5.
* </p>
* @param encodedMessage encoded message containing exactly one sub-frame
* @return sub-frame with TLM and HOW fields already set up
* @see SubFrame1
* @see SubFrame2
* @see SubFrame3
* @see SubFrame4A0
* @see SubFrame4A1
* @see SubFrame4B
* @see SubFrame4C
* @see SubFrame4D
* @see SubFrame4E
* @see SubFrameAlmanac
* @see SubFrameDummyAlmanac
*/
public static SubFrame parse(final EncodedMessage encodedMessage) {
encodedMessage.start();
// get the raw words
final int[] words = new int[10];
for (int i = 0; i < words.length; ++i) {
words[i] = (int) encodedMessage.extractBits(30);
}
// check parity on all words
for (int i = 0; i < words.length; ++i) {
// we assume last word of previous sub frame had parity bits set to 0,
// using the non_information bits at the end of each sub-frame
if (!checkParity(i == 0 ? 0x0 : words[i - 1], words[i])) {
throw new OrekitException(OrekitMessages.GNSS_PARITY_ERROR, i + 1);
}
}
final int id = (words[1] >>> 8) & 0x7;
switch (id) {
case 1 : // IS-GPS-200 figure 40-1 sheet 1
return new SubFrame1(words);
case 2 : // IS-GPS-200 figure 40-1 sheet 2
return new SubFrame2(words);
case 3 : // IS-GPS-200 figure 40-1 sheet 3
return new SubFrame3(words);
case 4 : {
final int svId = (words[2] >>> 22) & 0x3F;
// see table 20-V for mapping between SV-ID and page format
switch (svId) {
case 0 : // almanac for dummy Sv
return new SubFrameDummyAlmanac(words);
case 57 : // pages 1, 6, 11, 16, 21
// IS-GPS-200 figure 40-1 sheet 6
return new SubFrame4A0(words);
case 25 : case 26 : case 27 : case 28 : case 29 : case 30 : case 31 : case 32 : // pages 2, 3, 4, 5, 7, 8, 9, 10
// IS-GPS-200 figure 40-1 sheets 4 is also applicable to sub-frame 4
return new SubFrameAlmanac(words);
case 53 : case 54 : case 55 : // pages 14, 15, 17
// IS-GPS-200 figure 40-1 sheet 11
return new SubFrame4B(words);
case 58 : case 59 : case 60 : case 61 : case 62 : // pages 12, 19, 20, 22, 23, 24
// IS-GPS-200 figure 40-1 sheet 7
return new SubFrame4A1(words);
case 52 : // page 13
// IS-GPS-200 figure 40-1 sheet 10
return new SubFrame4C(words);
case 56 : // page 18
// IS-GPS-200 figure 40-1 sheet 8
return new SubFrame4D(words);
case 63 : // page 25
// IS-GPS-200 figure 40-1 sheet 9
return new SubFrame4E(words);
default :
throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, svId);
}
}
case 5 : {
// IS-GPS-200 figure 40-1 sheets 4 and 5
final int page = (words[2] >>> 22) & 0x3F;
return page == 25 ? new SubFrame5B(words) : new SubFrameAlmanac(words);
}
default : throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, id);
}
}
/** Check parity.
* <p>
* This implements algorithm in table 20-XIV from IS-GPS-200N
* </p>
* @param previous previous 30 bits word (only two least significant bits are used)
* @param current current 30 bits word
* @return true if parity check succeeded
*/
public static boolean checkParity(final int previous, final int current) {
final int d29Star = previous & 0x2;
final int d30Star = previous & 0x1;
final int d25 = 0x1 & Integer.bitCount(d29Star | (current & 0x3B1F3480)); // 111011000111110011010010000000
final int d26 = 0x1 & Integer.bitCount(d30Star | (current & 0x1D8F9A40)); // 011101100011111001101001000000
final int d27 = 0x1 & Integer.bitCount(d29Star | (current & 0x2EC7CD00)); // 101110110001111100110100000000
final int d28 = 0x1 & Integer.bitCount(d30Star | (current & 0x1763E680)); // 010111011000111110011010000000
final int d29 = 0x1 & Integer.bitCount(d30Star | (current & 0x2BB1F340)); // 101011101100011111001101000000
final int d30 = 0x1 & Integer.bitCount(d29Star | (current & 0x0B7A89C0)); // 001011011110101000100111000000
final int parity = ((((d25 << 1 | d26) << 1 | d27) << 1 | d28) << 1 | d29) << 1 | d30;
return (parity & 0x3F) == (current & 0x3F);
}
/** Check if the sub-frame has parity errors.
* @return true if frame has parity errors
*/
public boolean hasParityErrors() {
return false;
}
/** Get a field.
* <p>
* The field indices are defined as constants in the various sub-frames classes.
* </p>
* @param fieldIndex field index (counting from 0)
* @return field value
*/
protected int getField(final int fieldIndex) {
return fields[fieldIndex];
}
/** Set a field.
* @param fieldIndex field index (counting from 0)
* @param wordIndex word index (counting from 1, to match IS-GPS-200 tables)
* @param shift right shift to apply (i.e. number of LSB bits for next fields that should be removed)
* @param nbBits number of bits in the field
* @param words raw 30 bits words
*/
protected void setField(final int fieldIndex, final int wordIndex,
final int shift, final int nbBits,
final int[] words) {
fields[fieldIndex] = (words[wordIndex - 1] >>> shift) & ((0x1 << nbBits) - 1);
}
/** Set a field.
* @param fieldIndex field index (counting from 0)
* @param wordIndexMSB word index containing MSB (counting from 1, to match IS-GPS-200 tables)
* @param shiftMSB right shift to apply to MSB (i.e. number of LSB bits for next fields that should be removed)
* @param nbBitsMSB number of bits in the MSB
* @param wordIndexLSB word index containing LSB (counting from 1, to match IS-GPS-200 tables)
* @param shiftLSB right shift to apply to LSB (i.e. number of LSB bits for next fields that should be removed)
* @param nbBitsLSB number of bits in the LSB
* @param words raw 30 bits words
*/
protected void setField(final int fieldIndex,
final int wordIndexMSB, final int shiftMSB, final int nbBitsMSB,
final int wordIndexLSB, final int shiftLSB, final int nbBitsLSB,
final int[] words) {
final int msb = (words[wordIndexMSB - 1] >>> shiftMSB) & ((0x1 << nbBitsMSB) - 1);
final int lsb = (words[wordIndexLSB - 1] >>> shiftLSB) & ((0x1 << nbBitsLSB) - 1);
fields[fieldIndex] = msb << nbBitsLSB | lsb;
}
/** Get telemetry preamble.
* @return telemetry preamble
*/
public int getPreamble() {
return getField(PREAMBLE);
}
/** Get telemetry message.
* @return telemetry message
*/
public int getMessage() {
return getField(MESSAGE);
}
/** Get integrity status flag.
* @return integrity status flag
*/
public int getIntegrityStatus() {
return getField(INTEGRITY_STATUS);
}
/** Get Time Of Week of next 12 second message.
* @return Time Of Week of next 12 second message (s)
*/
public int getTow() {
return getField(TOW_COUNT) * 6;
}
/** Get alert flag.
* @return alert flag
*/
public int getAlert() {
return getField(ALERT);
}
/** Get anti-spoofing flag.
* @return anti-spoofing flag
*/
public int getAntiSpoofing() {
return getField(ANTI_SPOOFING);
}
/** Get sub-frame id.
* @return sub-frame id
*/
public int getId() {
return getField(ID);
}
}