SubFrame.java

  1. /* Copyright 2022-2025 Thales Alenia Space
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.gnss.rflink.gps;

  18. import org.orekit.errors.OrekitException;
  19. import org.orekit.errors.OrekitMessages;
  20. import org.orekit.gnss.metric.parser.EncodedMessage;

  21. /**
  22.  * Container for sub-frames in a GPS navigation message.
  23.  * @author Luc Maisonobe
  24.  * @since 12.0
  25.  */
  26. public abstract class SubFrame  {

  27.     /** TLM preamble. */
  28.     public static final int PREAMBLE_VALUE = 0x8b;

  29.     /** Words size. */
  30.     protected static final int WORD_SIZE = 30;

  31.     /** Size of parity field. */
  32.     protected static final int PARITY_SIZE = 6;

  33.     /** TLM preamble index. */
  34.     private static final int PREAMBLE = 0;

  35.     /** Telemetry message index. */
  36.     private static final int MESSAGE = 1;

  37.     /** Integrity status flag index. */
  38.     private static final int INTEGRITY_STATUS = 2;

  39.     /** Truncated Time Of Week count index. */
  40.     private static final int TOW_COUNT = 3;

  41.     /** Alert flag index. */
  42.     private static final int ALERT = 4;

  43.     /** Anti-spoofing flag index. */
  44.     private static final int ANTI_SPOOFING = 5;

  45.     /** Sub-frame ID index. */
  46.     private static final int ID = 6;

  47.     /** Raw data fields. */
  48.     private final int[] fields;

  49.     /** Simple constructor.
  50.      * @param words raw words
  51.      * @param nbFields number of fields in the sub-frame
  52.      * (including TLM and HOW data fields, excluding non-information and parity)
  53.      */
  54.     protected SubFrame(final int[] words, final int nbFields) {

  55.         this.fields = new int[nbFields];

  56.         // common fields present in telemetry and handover words for all sub-frames
  57.         setField(PREAMBLE,         1, 22,  8, words);
  58.         setField(MESSAGE,          1,  8, 14, words);
  59.         setField(INTEGRITY_STATUS, 1,  7,  1, words);
  60.         setField(TOW_COUNT,        2, 13, 17, words);
  61.         setField(ALERT,            2, 12,  1, words);
  62.         setField(ANTI_SPOOFING,    2, 11,  1, words);
  63.         setField(ID,               2,  8,  3, words);

  64.         if (getField(PREAMBLE) != PREAMBLE_VALUE) {
  65.             throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, getField(PREAMBLE));
  66.         }

  67.     }

  68.     /** Builder for sub-frames.
  69.      * <p>
  70.      * This builder creates the proper sub-frame type corresponding to the ID in handover word
  71.      * and the SV Id for sub-frames 4 and 5.
  72.      * </p>
  73.      * @param encodedMessage encoded message containing exactly one sub-frame
  74.      * @return sub-frame with TLM and HOW fields already set up
  75.      * @see SubFrame1
  76.      * @see SubFrame2
  77.      * @see SubFrame3
  78.      * @see SubFrame4A0
  79.      * @see SubFrame4A1
  80.      * @see SubFrame4B
  81.      * @see SubFrame4C
  82.      * @see SubFrame4D
  83.      * @see SubFrame4E
  84.      * @see SubFrameAlmanac
  85.      * @see SubFrameDummyAlmanac
  86.      */
  87.     public static SubFrame parse(final EncodedMessage encodedMessage) {

  88.         encodedMessage.start();

  89.         // get the raw words
  90.         final int[] words = new int[10];
  91.         for (int i = 0; i < words.length; ++i) {
  92.             words[i] = (int) encodedMessage.extractBits(30);
  93.         }

  94.         // check parity on all words
  95.         for (int i = 0; i < words.length; ++i) {
  96.             // we assume last word of previous sub frame had parity bits set to 0,
  97.             // using the non_information bits at the end of each sub-frame
  98.             if (!checkParity(i == 0 ? 0x0 : words[i - 1], words[i])) {
  99.                 throw new OrekitException(OrekitMessages.GNSS_PARITY_ERROR, i + 1);
  100.             }
  101.         }

  102.         final int id = (words[1] >>>  8) & 0x7;
  103.         switch (id) {
  104.             case 1 : // IS-GPS-200 figure 40-1 sheet 1
  105.                 return new SubFrame1(words);
  106.             case 2 : // IS-GPS-200 figure 40-1 sheet 2
  107.                 return new SubFrame2(words);
  108.             case 3 : // IS-GPS-200 figure 40-1 sheet 3
  109.                 return new SubFrame3(words);
  110.             case 4 : {
  111.                 final int svId = (words[2] >>> 22) & 0x3F;
  112.                 // see table 20-V for mapping between SV-ID and page format
  113.                 switch (svId) {
  114.                     case 0  : // almanac for dummy Sv
  115.                         return new SubFrameDummyAlmanac(words);
  116.                     case 57 : // pages 1, 6, 11, 16, 21
  117.                         // IS-GPS-200 figure 40-1 sheet 6
  118.                         return new SubFrame4A0(words);
  119.                     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
  120.                         // IS-GPS-200 figure 40-1 sheets 4 is also applicable to sub-frame 4
  121.                         return new SubFrameAlmanac(words);
  122.                     case 53 : case 54 : case 55 : // pages 14, 15, 17
  123.                         // IS-GPS-200 figure 40-1 sheet 11
  124.                         return new SubFrame4B(words);
  125.                     case 58 : case 59 : case 60 : case 61 : case 62 : // pages 12, 19, 20, 22, 23, 24
  126.                         // IS-GPS-200 figure 40-1 sheet 7
  127.                         return new SubFrame4A1(words);
  128.                     case 52 : // page 13
  129.                         // IS-GPS-200 figure 40-1 sheet 10
  130.                         return new SubFrame4C(words);
  131.                     case 56 : // page 18
  132.                         // IS-GPS-200 figure 40-1 sheet 8
  133.                         return new SubFrame4D(words);
  134.                     case 63 : // page 25
  135.                         // IS-GPS-200 figure 40-1 sheet 9
  136.                         return new SubFrame4E(words);
  137.                     default :
  138.                         throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, svId);
  139.                 }
  140.             }
  141.             case 5 : {
  142.                 // IS-GPS-200 figure 40-1 sheets 4 and 5
  143.                 final int page = (words[2] >>> 22) & 0x3F;
  144.                 return page == 25 ? new SubFrame5B(words) : new SubFrameAlmanac(words);
  145.             }
  146.             default : throw new OrekitException(OrekitMessages.INVALID_GNSS_DATA, id);
  147.         }

  148.     }

  149.     /** Check parity.
  150.      * <p>
  151.      * This implements algorithm in table 20-XIV from IS-GPS-200N
  152.      * </p>
  153.      * @param previous previous 30 bits word (only two least significant bits are used)
  154.      * @param current current 30 bits word
  155.      * @return true if parity check succeeded
  156.      */
  157.     public static boolean checkParity(final int previous, final int current) {

  158.         final int d29Star = previous & 0x2;
  159.         final int d30Star = previous & 0x1;

  160.         final int d25     = 0x1 & Integer.bitCount(d29Star | (current & 0x3B1F3480)); // 111011000111110011010010000000
  161.         final int d26     = 0x1 & Integer.bitCount(d30Star | (current & 0x1D8F9A40)); // 011101100011111001101001000000
  162.         final int d27     = 0x1 & Integer.bitCount(d29Star | (current & 0x2EC7CD00)); // 101110110001111100110100000000
  163.         final int d28     = 0x1 & Integer.bitCount(d30Star | (current & 0x1763E680)); // 010111011000111110011010000000
  164.         final int d29     = 0x1 & Integer.bitCount(d30Star | (current & 0x2BB1F340)); // 101011101100011111001101000000
  165.         final int d30     = 0x1 & Integer.bitCount(d29Star | (current & 0x0B7A89C0)); // 001011011110101000100111000000

  166.         final int parity  = ((((d25 << 1 | d26) << 1 | d27) << 1 | d28) << 1 | d29) << 1 | d30;

  167.         return (parity & 0x3F) == (current & 0x3F);

  168.     }

  169.     /** Check if the sub-frame has parity errors.
  170.      * @return true if frame has parity errors
  171.      */
  172.     public boolean hasParityErrors() {
  173.         return false;
  174.     }

  175.     /** Get a field.
  176.      * <p>
  177.      * The field indices are defined as constants in the various sub-frames classes.
  178.      * </p>
  179.      * @param fieldIndex field index (counting from 0)
  180.      * @return field value
  181.      */
  182.     protected int getField(final int fieldIndex) {
  183.         return fields[fieldIndex];
  184.     }

  185.     /** Set a field.
  186.      * @param fieldIndex field index (counting from 0)
  187.      * @param wordIndex word index (counting from 1, to match IS-GPS-200 tables)
  188.      * @param shift right shift to apply (i.e. number of LSB bits for next fields that should be removed)
  189.      * @param nbBits number of bits in the field
  190.      * @param words raw 30 bits words
  191.      */
  192.     protected void setField(final int fieldIndex, final int wordIndex,
  193.                             final int shift, final int nbBits,
  194.                             final int[] words) {
  195.         fields[fieldIndex] = (words[wordIndex - 1] >>> shift) & ((0x1 << nbBits) - 1);
  196.     }

  197.     /** Set a field.
  198.      * @param fieldIndex field index (counting from 0)
  199.      * @param wordIndexMSB word index containing MSB (counting from 1, to match IS-GPS-200 tables)
  200.      * @param shiftMSB right shift to apply to MSB (i.e. number of LSB bits for next fields that should be removed)
  201.      * @param nbBitsMSB number of bits in the MSB
  202.      * @param wordIndexLSB word index containing LSB (counting from 1, to match IS-GPS-200 tables)
  203.      * @param shiftLSB right shift to apply to LSB (i.e. number of LSB bits for next fields that should be removed)
  204.      * @param nbBitsLSB number of bits in the LSB
  205.      * @param words raw 30 bits words
  206.      */
  207.     protected void setField(final int fieldIndex,
  208.                             final int wordIndexMSB, final int shiftMSB, final int nbBitsMSB,
  209.                             final int wordIndexLSB, final int shiftLSB, final int nbBitsLSB,
  210.                             final int[] words) {
  211.         final int msb = (words[wordIndexMSB - 1] >>> shiftMSB) & ((0x1 << nbBitsMSB) - 1);
  212.         final int lsb = (words[wordIndexLSB - 1] >>> shiftLSB) & ((0x1 << nbBitsLSB) - 1);
  213.         fields[fieldIndex] = msb << nbBitsLSB | lsb;
  214.     }

  215.     /** Get telemetry preamble.
  216.      * @return telemetry preamble
  217.      */
  218.     public int getPreamble() {
  219.         return getField(PREAMBLE);
  220.     }

  221.     /** Get telemetry message.
  222.      * @return telemetry message
  223.      */
  224.     public int getMessage() {
  225.         return getField(MESSAGE);
  226.     }

  227.     /** Get integrity status flag.
  228.      * @return integrity status flag
  229.      */
  230.     public int getIntegrityStatus() {
  231.         return getField(INTEGRITY_STATUS);
  232.     }

  233.     /** Get Time Of Week of next 12 second message.
  234.      * @return Time Of Week of next 12 second message (s)
  235.      */
  236.     public int getTow() {
  237.         return getField(TOW_COUNT) * 6;
  238.     }

  239.     /** Get alert flag.
  240.      * @return alert flag
  241.      */
  242.     public int getAlert() {
  243.         return getField(ALERT);
  244.     }

  245.     /** Get anti-spoofing flag.
  246.      * @return anti-spoofing flag
  247.      */
  248.     public int getAntiSpoofing() {
  249.         return getField(ANTI_SPOOFING);
  250.     }

  251.     /** Get sub-frame id.
  252.      * @return sub-frame id
  253.      */
  254.     public int getId() {
  255.         return getField(ID);
  256.     }

  257. }