Last week I presented a JT4G detection algorithm intended to detect very weak signals from DSLWP-B, down to -25dB SNR in 2500Hz. I have now processed the recordings of yesterday’s transmissions with this algorithm and here I look at the results. I have also made a Python script with the algorithm so that people can process their recordings easily. Instructions are included in this post.

The JT4G transmissions are made just after the end of each GMSK telemetry packet, as illustrated by the following figure made by Cees Bassa using the signals he received at Dwingeloo. Also note that the JT4G signal starts at an UTC minute, as it is common with WSJT-X modes. The frequency of the lowest JT4G tone seems to be 1kHz higher than the GMSK carrier.

As far as I know, the following stations have shared recordings of the JT4G signals: IW1DTU, BG6LQV, and JA0CAW.

An interesting thing about the JT4G signal transmitted by DSLWP-B is that a tone separation of 312.5Hz is used instead of the standard 315Hz. This is due to hardware limitations. Wei Mingchuan BG2BHC warned me about this (note that there is a mistake in his tweet) and I was able to confirm this using IW1DTU’s high SNR recording. I have needed to adjust my detection algorithm to account for this difference.

A tone separation of 315Hz is used in standard JT4G because 315Hz is an integer multiple of the baudrate (4.375Hz). Therefore, when using an FFT size of one symbol, the tone separation will be an integer number of FFT bins, simplifying things. For a tone separation of 312.5Hz, there are several ways of adjusting things. An option is to use an FFT size such that 312.5Hz is an integer number of bins. This has the disadvantage that each transform no longer spans a single symbol, which can give other kinds of problems. It is also possible to use the same FFT size and round each of the tone frequencies to the nearest integer FFT bin. Finally, another option is to generate each of the tone frequencies using a complex exponential instead of a shift of the FFT. This has the advantage that the tone frequencies are generated exactly, but however the number of FFTs to compute increases by a factor of four.

After some experimentation I have decided to use the second option: use the same FFT size and round the tone separation to an integer number of FFT bins. Although the frequency shifts are only approximate, this gives a good result, as we will see below.

I have also added an SNR estimator. This is motivated by the fact that Wei and I have noted that the SNR estimator in WSJT-X 1.9.1 doesn’t work properly. I have tested it with signals generated using `jt49sim`

having different SNRs and the WSJT-X decoder always reports an SNR between -18dB and -16dB.

The algorithm I use to estimate SNR is the following. After the correlation peak in time and frequency has been found, the power spectra are summed to accumulate all four FSK tones into a single FFT bin (the bin size is still 4.375Hz). The noise power per bin is estimated by averaging several bins near the correlation frequency and taking into account that each bin contains the noise power corresponding to four bins. The signal plus noise power is computed by summing the power in the correlation frequency and its nearest four bins.

The Python script used to perform the detection is dslwp_jt4.py. It must be run as

dslwp_jt4.py input.wav output_path label

The input must be a WAV file whose sample rate is a multiple of 35Hz. Both IQ and real (single-channel) WAV files are supported. It is expected that a single JT4G transmission is contained in the file and that there are no strong interferers (any possible interference in neighbouring frequencies should be filtered out before running the detector). The `output_path`

is the path where the detector will store its output plots. The filename of each plot will be produced by appending some string, such as `_time.png`

to the `output_path`

, so set this accordingly. The `label`

is only used as a title in the plots.

An easy way to produce valid input files for the detector is to use WSJT-X recording capabilities (the “Save > Save all” menu option) and then convert them from 12000Hz to 11025Hz using sox as

sox input.wav output.wav rate 11025

The results of running the detector on the recordings linked above are as follows. The recording by Fer IW1DTU has a rather high SNR of -12dB.

The antenna used by IW1DTU is an array of 4 yagis of 23 elements, with a theoretical gain of 23dBi.

The signal received by BG6LQV is weak, at -20dB.

He is using a 10-turn helix antenna. Its gain should be around 11dBic.

The two recordings shared by Tetsu JA0CAW have an SNR of -18dB. I don’t know what antenna he is using.

Note that in the frequency plots above the correlation energy is concentrated almost in a single frequency bin. This means that rounding the frequency shifts to an integer number of bins works well.

The important question now is what is the smallest antenna that could be used to detect DSLWP-B’s JT4G signals. In particular, I want to try luck with my 7 element Arrow yagi. This should have a gain of about 12dBi, so looking at BG6LQV’s results it looks quite feasible.

I’m interested in reports from other stations, particularly from the smaller ones. If you have a small 70cm yagi, try to listen during the next JT4G test, run WSJT-X and record all data as WAV. Then share your data and/or run my detector script.

]]>As you may know, the DSLWP-B satellite is now orbiting the Moon since May 25 and the first Amateur VLBI session was performed last Sunday. The groundstations at Shahe in Beijing, China, and Dwingeloo in the Netherlands performed a GPS-synchronized recording of the 70cm signals from DSLWP-B from 04:20 to 5:40 UTC on 2018-06-10. I have adapted my VLBI correlation algorithms and processed these recordings. Here are my first results.

The baseline for these VLBI recordings (i.e., the distance between the groundstations) is roughly 7250km. The signals transmitted by DSLWP-B are 250bps GMSK using an \(r=1/2\) turbo code. Two transmit frequencies are used: 435.4MHz and 436.4MHz. Each transmit frequency uses a different antenna. The antenna marked below as UV Antenna A is used for 435.4MHz, while the UV Antenna B is used for 436.4MHz.

The transmissions are done in packets. A packet lasts about 15 seconds and is transmitted roughly every 5 minutes. Packets are transmitted simultaneously in both frequencies, but the data transmitted in each of the frequencies is different.

The format of the recordings is as follows. Each recording is 20 seconds long (or 19 seconds in some cases) and contains a single packet. It is formed by two files, one for 435.4MHz and the other for 436.4MHz. Each of these files is an IQ file at 40ksps centred at 435.4MHz or 436.4MHz. The format of the files is the GNURadio metadata format. The file metadata can be read with `gr_read_file_metadata`

and contains the UTC timestamps for the IQ stream. These timestamps are needed to synchronize the recordings made at both groundstations, since their start times can be off by a fraction of a second.

The recordings made at Dwingeloo can be downloaded from the CAMRAS data repostiory, while the recordings made at Shahe can be downloaded here.

The correlation algorithm I’m using works as follows. The input of the algorithm is a pair of recordings for the same frequency and time, one at each groundstation. First, ephemeris data computed using GMAT is used to get the approximate Doppler at each of the groundstations. This approximated Doppler is corrected by subtracting 200Hz (since it seems that the DSLWP-B transmitter is not exactly on frequency) and then this estimate is used to translate in frequency each of the recordings to bring the signals to baseband. The signals are then lowpass filtered to a cut-off frequency of 500Hz by a FIR filter with 512 taps. These two signals are correlated in frequency and time using the following method.

Let us denote by \(x_1\) and \(x_2\) each of the signals, by \(\mathcal{F}\) the Fourier transform operator and by \(S\) the operator defined by a circular shift \((Sx)(j) = x(j-1)\) (where indices are understood modulo the length of the signal). Then we compute\[y_n = \mathcal{F}^{-1}(\mathcal{F}x_1\cdot\overline{S^{n}\mathcal{F}x_2}),\qquad -N \leq n \leq N,\]where \(N\) sets the range of frequencies to search for correlation. We search for the \(j\) and \(n\) that maximize \(|y_n(j)|\). These \(j\) and \(n\) give respectively the time offset (in samples) and frequency offset (in Fourier bins) between both signals.

Note that the length of the Fourier transforms is the whole signal. This means that the signal is integrated coherently for its whole duration. While this may seem problematic, I have checked that the coherence of the received signal is quite good and most of the correlation is concentrated in a single Fourier bin, so it is possible to integrate coherently the whole signal.

This also gives very good resolution (0.05Hz). Whereas my LilacSat-2 correlation used several shorter correlations and used the phase of the correlations to refine the frequency, here we get the frequency directly from the Fourier bin that maximizes correlation. In this aspect, the signal processing for DSLWP-B is much more simple.

The value of \(N\) is adjusted to give an appropriate range of frequency search, which depends on the accuracy of the Doppler estimate performed above. Currently it is set to \(\pm 10\mathrm{Hz}\).

Finally, the time offset and frequency offset can be transformed into delta-range (correcting with the clock offset between receivers) and delta-velocity. To refine the time offset and obtain sub-sample resolution, the parabola peak estimator introduced in the LilacSat-2 advanced processing post is used. Since the baudrate of the signals is only 250bps, the precision of delta-range measurements is rather low, around 20 or 30km. I am already thinking about ways to improve the sub-sample resolution estimator, but whether these will give improved precision remains to be tested. In contrast, the precision of delta-velocity measurements is very good, since the phase of the signal is quite stable.

The correlation algorithm is implemented in the vlbi.py Python script, which performs the correlations and saves the results in an `npz`

file for later analysis. To run, it needs the UTC timestamps corresponding to the start of each recording, which can be obtained using `gr_read_file_metadata`

. It also needs the ephemeris output (or Doppler file) produced by running the GMAT script dslwp_vlbi.script. Moreover, the input of `vlbi.py`

needs to be regular (`complex64`

) IQ files instead of metadata files, so these must be converted using convert_metadata_file.py.

The procedure of unpacking and selecting the recordings, converting the files, extracting UTC timestamps and performing correlations is automated by the shell script process_vlbi.sh. You must edit this script to set the location of the tools described above in the `PATH`

and to enter the location of the GMAT Doppler file. Also note that this script has no error handling, so if things go as expected it will work, but if not it will certainly produce lots of errors.

After running `process_vlbi.sh`

, the results are analysed in this Jupyter notebook. The plots below show the results from the VLBI measurements in each of the frequencies, compared with the prediction obtained using GMAT. The orbital state used for the prediction is the one I derived in my latest orbit determination post. No corrections for the finite speed of light or other effects have been done.

Note that the delta-range measurement is very noisy, since it uses “code measurements” and the code rate is only 250Hz. In fact, the estimates obtained in the two frequencies always differ by several tens of kilometers. In comparison, the delta-velocity measurement is very precise and both frequencies give almost the same result.

The plots below show the residuals, which are the difference between the measurements and the prediction by GMAT.

Finally, the plot below shows the magnitude of the correlation peak. We see that the strength of the signals remains more or less constant, but the 435.4Mhz signal is much weaker than the 436.4MHz signal. This effect has been already observed in most experiments and it is due to the attitude of DSWLP-B being unfavourable to the location of antenna A, according to the relative positions of the Earth, Moon and Sun (where DSWLP-B’s solar panel is pointing to during normal flight).

So far this is just a first look at the VLBI data. There is much experimentation that can be done and a lot of ideas that can be applied to this data.

]]>As far as I know, there have been no tests using JT4G yet. According to the documentation of WSJT-X 1.9.0, JT4G can be decoded down to -17dB SNR measured in 2.5kHz bandwidth. However, if we don’t insist on decoding the data, but only detecting the signal, much weaker signals can be detected. The algorithm presented here achieves reliable detections down to about -25dB SNR, or 9dB C/N0.

This possibility is very interesting, because it enables very modest stations to detect signals from DSLWP-B. In comparison, the r=1/2 turbo code can achieve decodes down to 1dB Eb/N0, or 25dB C/N0. In theory, this makes detection of JT4G signals 16dB easier than decoding the GMSK telemetry. Thus, very small stations should be able to detect JT4G signals from DSLWP-B.

As described in the WSJT-X documentation, the JT4 family uses 4FSK at 4.375baud. A total of 206 symbols are transmitted over the 47.09s that a complete message lasts. A pseudo-random 206 bit sync vector is used for time and frequency synchronization. The sync vector is the following (note that there is an error in the WSJT-X documentation):

000110001101100101000000011000000000000101101101011111010001 001001111100010100011110110010001101010101011111010101101010 111001011011110000110110001110111011100100011011001000111111 00110000110001011011110101

The autocorrelation function of this sequence is shown below, both in a linear scale and in dB units.

The autocorrelation function has rather strong sidelobes of around -8dB. These could be reduced by using a well designed sync vector. However, the strength of the sidelobes is not important for any reasonable use of the JT4 or other similar modes.

A JT4 message contains 72 information bits, encoded using a k=32, r=1/2 convolutional code with zero-tailing, yielding 206 FEC symbols. These FEC symbols are transmitted together with the sync vector as follows: the sync vector symbol is encoded as the least significant bit of the 4FSK symbol and the FEC symbol is encoded as the most significant bit of the 4FSK symbol. In other words, if we number the four FSK tones as \(T_0\), \(T_1\), \(T_2\), \(T_3\), then the sync vector symbol chooses between \(\{T_0, T_2\}\) and \(\{T_1, T_3\}\), and the FEC symbol chooses between \(\{T_0, T_2\}\) and \(\{T_1, T_3\}\). Therefore, 50% of the message power is devoted to synchronization. This enables us to perform detections at a very low SNR.

The idea of the synchronization algorithm is to compute, for each symbol \(n\) and each tone \(j\) the corresponding power \(P(T_{j,n})\) and compute the sequence\[x_n = P(T_{1,n}) + P(T_{3,n}) – P(T_{0,n}) – P(T_{2,n}).\]

Then the sequence \(x_n\) is correlated against the bipolar sequence \(s_n\) obtained from the sync vector. This correlation gives a strong peak at the correct delay, regardless of what data has been transmitted.

This is the algorithm idea. To transform it into a complete algorithm, we must perform some form of frequency and time synchronization. First, an FFT is applied to the signal. The FFT size is chosen to yield a frequency resolution of 4.375Hz, so that a single 4FSK symbol fits exactly in a Fourier transform. This limits the choice of the the sample rate to multiples of 35Hz. To provide finer time synchronization, overlapping FFT transforms are made, using a 50% of overlap. We denote as \(f_{k,n}\) the \(k\)-th frequency bin of the \(n\)-th FFT.

The tone separation for JT4G is \(S=72\) bins. We compute\[x_{k,n} = |f_{k+S,n}|^2 + |f_{k+3S,n}|^2 – |f_{k,n}|^2 + |f_{k+2S,n}|^2.\] Finally, for each \(k\), the sequences \(x_{k,2j}\) and \(x_{k,2j+1}\) are correlated against \(s_j\). This yields a correlation peak for the \(k\) corresponding to the frequency of the lowest tone \(T_0\) and the delay \(j\) corresponding to the start of the JT4G message.

The figures below show the correlations for a -25dB SNR signal generated with `jt4sim`

(see this post for more information on simulating WSJT-X signals).

The calculations used in this post have been performed in this Jupyter notebook. Let’s stay tuned for the first DSLWP-B JT4G tests to check the performance of this algorithm with real recordings.

The algorithm presented here assumes no previous knowledge of the data transmitted in the JT4G message. Perhaps DSLWP-B transmits always the same data and this can be used to improve even further the sensitivity of this detection algorithm.

]]>Below I show the Keplerian state that was determined on 2018-06-03, in comparison with the new state determined on 2018-06-10 (both are referenced to the same epoch of 2018-05-26 00:00:00 UTC).

% 20180603 %DSLWP_B.SMA = 8761.0758581 %DSLWP_B.ECC = 0.768016853537 %DSLWP_B.INC = 16.9728174682 %DSLWP_B.RAAN = 295.670653562 %DSLWP_B.AOP = 130.427472407 %DSLWP_B.TA = 178.126596496 % 20180610 DSLWP_B.SMA = 8762.40279943 DSLWP_B.ECC = 0.764697135746 DSLWP_B.INC = 18.6101083906 DSLWP_B.RAAN = 297.248156986 DSLWP_B.AOP = 130.40460851 DSLWP_B.TA = 178.09494681

It seems that there is still an indetermination of a few degrees in the inclination and right-ascension of the ascending node and a few kilometres in the semi-major axis.

The graph below shows the Doppler fit.

The Jupyter notebook where these calculations are performed can be found here.

]]>In STARcon 2018, Julián Santamaría from AEMET (the Spanish meteorological office) gave me an RS41. While I have some long-term ideas about how to use it as a propagation sounder, I have just started playing with it. In this brief note, I explain how to flash the radiosonde with custom firmware.

The particular radiosonde I have has serial number N2720012. The CPU is an STM32F100C8T6B with 64kB of flash, the radio transmitter is a SiLabs Si4032 and the GPS receiver is an uBlox UBX-G6010-ST.

To flash the STM32F1, an STM32 programmer is needed. I have used an ST-LINK/V2 Chinese clone, which is very inexpensive in eBay and similar sites. The RS41 has a pin header that exposes the programming lines, so the ST-LINK can be connected directly to this header. The pinout of can be seen here. I reproduce it below for completeness.

RS41 header: | ----------- | | 2 4 6 8 10 | | 1 3 5 7 9 | | ---- ---- | Programer cable connections: RS41 ----- ST-LINK =================== Pin 1 ----- GND Pin 5 ----- 5.0V Pin 8 ----- SWCLK Pin 9 ----- SWDIO

The flash of the RS41 is read and write protected. This means that there is no easy way of dumping the factory firmware. The protection can be removed by issuing a command to the STM32F1, but the flash gets erased when the protection is disabled. The easiest way to remove the protection is with ST-LINK Utility. Presumably it should also be possible to do it with openocd. See here for more information on the protection.

To program the STM32F1 I’m using stlink. It is also possible to use openocd. As Dan Gisselquist, from ZipCPU, says, the first step of any embedded project should be to blink and LED. In this way we can check that our toolchain and programmer are working correctly.

After blinking an LED, many people would probably like to flash the RS41HUP firmware, which transmits RTTY and ARPS in the 70cm Amateur band.

To blink an LED in the RS41, I’m using the blink-plain template from stm32-templates, which also includes an arm-none-eabi toolchain for amd64, in case you don’t want to install your own toolchain.

It is necessary to change all occurrences of `GPIOC`

to `GPIOB`

in `src/main.c`

to get the red LED flashing, since it is connected to `GPIO_Pin_8`

in `GPIOB`

. The green LED is connected to `GPIO_Pin_7`

, also in `GPIOB`

.

After compiling the code with the Makefile included in the template, we can flash it as follows:

$ arm-none-eabi-objcopy -O binary blink-plain.elf blink-plain.bin $ st-flash write blink-plain.bin 0x8000000

It is also possibly to run the firmware under GDB in the following way:

$ st-util ... $ arm-none-eabi-gdb blink-plain.elf ... (gdb) target extended-remote :4242 ... (gdb) load ... (gdb) continue]]>

JPEG images are not so difficult to spot amongst binary data, since they contain JFIF in ASCII in their header (`4a 46 49 46`

in hex). Another telltale sign of JPEG is the `ff d8 ff`

that marks the start of the file. Presumably this is how Mike first noticed the images.

Mike has published some information about the image packets transmitted by 1KUNS-PF and he has been posting some of the images he receives.

Below there is the hex dump of the first and second packet CSP packet of a JPEG image (note the JFIF and `ff d8 ff`

inside the packet payload).

pdu_length = 138 contents = 0000: 00 e2 92 42 00 00 ff d8 ff e0 00 10 4a 46 49 46 0010: 00 01 01 01 00 00 00 00 00 00 ff db 00 43 00 0c 0020: 08 09 0b 09 08 0c 0b 0a 0b 0e 0d 0c 0e 12 1e 14 0030: 12 11 11 12 25 1a 1c 16 1e 2c 26 2e 2d 2b 26 2a 0040: 29 30 36 45 3b 30 33 41 34 29 2a 3c 52 3d 41 47 0050: 4a 4d 4e 4d 2f 3a 55 5b 54 4b 5a 45 4c 4d 4a ff 0060: db 00 43 01 0d 0e 0e 12 10 12 23 14 14 23 4a 32 0070: 2a 32 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 0080: 4a 4a 4a 4a 4a 4a fa 14 dc 9e pdu_length = 138 contents = 0000: 00 e2 92 42 00 01 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 0010: 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 4a 0020: 4a 4a 4a 4a ff c4 00 1f 00 00 01 05 01 01 01 01 0030: 01 01 00 00 00 00 00 00 00 00 01 02 03 04 05 06 0040: 07 08 09 0a 0b ff c4 00 b5 10 00 02 01 03 03 02 0050: 04 03 05 05 04 04 00 00 01 7d 01 02 03 00 04 11 0060: 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 0070: a1 08 23 42 b1 c1 15 52 d1 f0 24 33 62 72 82 09 0080: 0a 16 17 18 19 1a 95 64 b7 7e

It seems that all the image packets are 138 bytes long. The first four bytes are the CSP header. Then we have 16bit big-endian field which counts the number of chunk, starting by `00`

. The last four bytes of the packet are most likely a checksum, and the remaining bytes are the corresponding chunk of the JPEG file.

I don’t know how to detect the end of one image other than by taking note of the beginning of the next image. In fact, this is the last packet of this image:

pdu_length = 138 contents = 0000: 00 e2 92 42 00 47 8a 00 5a 28 00 a2 81 05 14 00 0010: 51 40 05 14 00 51 40 05 14 00 51 40 05 14 00 51 0020: 40 c2 8a 00 28 a0 02 8a 00 28 a0 04 a2 80 0a 28 0030: 18 51 40 05 14 00 51 40 05 14 00 51 40 05 14 c6 0040: 14 50 01 45 00 14 50 01 45 00 14 50 01 45 00 14 0050: 50 01 45 00 14 50 01 45 00 7f ff d9 00 00 00 00 0060: 00 00 00 00 00 11 41 42 00 48 00 00 04 58 00 00 0070: 00 10 00 00 00 51 d0 38 c9 54 05 ac 8f d7 00 00 0080: bc 24 00 00 bc 24 dc bd 44 b0

The `ff d9`

that occurs mid-packet is the end-of-file of the JPEG file. I don’t know what to make of the rest of the data following it. Since not all of it is zero, it doesn’t look as deliberate padding. It might be the case that the satellite is sending information left over in a buffer.

The image below is the whole JPEG file contained in the packets received by Mike. It was sent using 72 chunks, for a total of 9216 bytes (at 128 bytes per chunk) and its resolution is 640×480. Most of the other images sent by 1KUNS-PF are smaller.

]]>In this post we will examine the Keplerian elements of the orbits described by each of the tracking files published so far. We will also use Scott Tilley VE7TIL’s Doppler measurements of the S-band beacon of DSLWP-B to validate and determine the orbit.

New tracking files get published by Wei in the dslwp_dev Github repository. The HEAD of the master branch only contains the more recent tracking files, but older files can be extracted from the git commit history. So far, the following tracking files have been published:

program_tracking_dslwp-a_20180520_window1.txt program_tracking_dslwp-b_20180526.txt program_tracking_dslwp-b_20180528.txt program_tracking_dslwp-b_20180529.txt program_tracking_dslwp-b_20180531.txt program_tracking_dslwp-b_20180601.txt program_tracking_dslwp-b_20180602.txt program_tracking_dslwp-b_20180603.txt

The first of them describes the path from Earth to Moon, right after trans-lunar injection, and it has already been examined in part I and part II of this series. The remaining files describe the lunar orbit of DSLWP-B, starting with different epochs. Here we will look at these seven tracking files.

To examine the tracking files and determine if there are any noticeable changes between the orbits described by each of them, we use GMAT to propagate the orbit and compute Keplerian state elements for each file. As we did in part I, the first row of each tracking file is taken as the state of the spacecraft, and then the orbit is propagated first backwards until 2018-05-26 and then forwards for 10 days. The resulting Keplerian elements are plotted in Jupyter for comparison.

The calculations have been performed using this Jupyter notebook.

The plots below show the evolution of the semi-major axis and the mean anomaly for each of the tracking files. These are the orbital elements where differences in the orbit can be seen best.

The first thing we notice is that there is a mismatch in the mean anomaly. In the first two tracking files the mean anomaly is shifted in time roughly 1 hour and 46 minutes with respect to the remaining tracking files. This means that the first two tracking files predict that the time of periapsis is 1 hour and 46 minutes later than what the remaining tracking files imply. I already observed this in my preliminary analysis of Scott Tilley’s Doppler measurements and Wei commented that he had made a mistake when producing these two tracking files.

This is interesting because I had calculated the time of periapsis for lunar orbit in part II to be 2018-05-25 16:04:08 UTC, but this didn’t match too well the fact that injection presumably happened around 14:00 or 15:00 UTC. If we factor in this 1 hour and 46 minutes error, then the corrected time of periapsis matches the injection time much better. I leave as an exercise for the reader to redo the calculations in part II with the improved orbital parameters derived in this post.

If we look at the semi-major axis, we can make three groups of tracking files according to their behaviour. The first group is formed by the first two tracking files, with a semi-major axis that peaks at 8770km. The second group is formed by the tracking files 3 to 6, with a semi-major axis that peaks at 8780km (also note that the third tracking file is a little bit different from the rest). The third group is formed only by the last tracking file, with a semi-major axis that peaks at 8765km. We will comment on this difference later.

Another interesting thing to note is that there is some weird thing happening with the semi-major axis at periapsis. This kind of thing was first observed by Jonathan McDowell in the first tracking files describing the lunar orbit. I think I still haven’t understood well this phenomenon. Here the force model I have used for orbit propagation is a detailed model including lunar spherical harmonics up to degree and order 10 and point forces for the Sun and all the planets (except for Mercury and Pluto).

Now we turn to the analysis of Scott Tilley’s Doppler measurements. We take the tracking file from 20180531 as a reference for the orbit of DSLWP-B and use GMAT to simulate the Doppler, as received by Scott’s station.

GMAT has advanced features to simulate two-way Doppler, as produced when transmitting a carrier from a groundstation on Earth to a spacecraft’s transponder and back to another groundstation on Earth (see the tutorial on simulating DSN Range and Doppler data). This simulation includes effects such as the finite propagation speed of light, relativistic corrections, and ionospheric and tropospheric delays. However, there doesn’t seem to be such an advanced system to simulate one-way Doppler such as the one produced by observing DSLWP-B’s beacon from a groundstation on Earth.

Probably the closest we can get to simulate one-way Doppler with GMAT is to use GMAT’s simulation capabilities to measure two-way range-rate with speed of light and relativistic corrections disabled and divide the resulting range-rate by two to obtain the one-way range-rate. This two-way range-rate does its calculations as follows. Let \(R(t)\) denote the two-way range from the groundstation to the spacecraft at time \(t\), so that \(R(t) = 2r(t)\), where \(r(t)\) is the distance between the groundstation and the spacecraft at time \(t\). The two-way range-rate at time \(t\) is computed as \((R(t) – R(t-\delta))/\delta\), where \(\delta\) is known as the Doppler averaging interval. Dividing this two-way range-rate by two we get \((r(t) – r(t-\delta))/\delta\), which is the average value for the Doppler, ignoring relativistic effects and the finite propagation speed of light. Since the propagation time from the Moon to Earth is a bit over one second, it is not so important to consider the propagation time in these simulations, but it would be very important for interplanetary missions.

Another more simple way to calculate Doppler is to make GMAT output the position \(x(t)\) and velocity \(v(t)\) of DSLWP-B in a reference frame centred on Scott’s station. Then we can compute the Doppler as\[\frac{d}{dt}\|x(t)\| = \frac{\langle v(t), x(t)\rangle}{\|x(t)\|}.\]This ignores the relativistic effects, the propagation speed of light, and also the fact that Doppler is measured by averaging over a time interval. However, for short averaging times this calculation is a reasonable approximation.

We use GMAT to simulate Doppler using both approaches and plot the results together with the Doppler measurements by Scott Tilley. To get a good match, we have needed to shift down the Doppler measurements by 3400Hz. Probably this is caused by DSLWP-B’s S-band beacon not being precisely on its nominal frequency of 2275.222MHz (other people list 2275.224MHz as the nominal frequency).

Note that GMAT’s simulation using RangeRate only outputs data whenever DSLWP-B is visible from Scott’s station, while the simple method using \(x\) and \(v\) outputs data for all times.

We see that most of the data measured by Scott matches the simulation rather well. However, there are two time intervals where the match is not good. These probably indicate a frequency drift in DSLWP-B’s beacon (or perhaps in Scott’s receiver) and have been discarded for further analysis. Discarded points are marked in the graph above in red.

This also indicates that the orbital parameters derived from the 20180531 tracking file are more or less accurate. When I analysed the tracking file 20180526 I obtained a large discrepancy, due to an incorrect anomaly at epoch. It also indicates that the drift of the S-band beacon of DSLWP-B is sufficiently low that orbit determination using this data is possible.

Now we use GMAT’s orbit determination capabilities to refine the orbital parameters using Scott’s data. I suggest the reader to take a look at the orbit estimation tutorial.

We use the following initial state for the orbit determination, which has been generated by propagating the 20180531 tracking file.

DSLWP_B.Epoch = '28264.5'; DSLWP_B.SMA = 8765.409054517644; DSLWP_B.ECC = 0.7618824709853163; DSLWP_B.INC = 20.80912899224475; DSLWP_B.RAAN = 307.3706391221838; DSLWP_B.AOP = 118.7406568683716; DSLWP_B.TA = 178.2429103785479;

As an error model for the Doppler measurements we use a \(\sigma\) of 0.003km/s, which corresponds to a Doppler shift of around 20Hz. This seems to be approximately the resolution of Scott’s measurements.

The orbit estimation converges, giving the following output:

Estimation converged! |1 - RMSP/RMSB| = | 1- 3.49511 / 3.49512| = 2.12288e-06 is less than RelativeTol, 0.0001 Final Estimated State: Estimation Epoch: 28264.5004286386756575666368 A.1 modified Julian 28264.5004282407393149740443 TAI modified Julian 26 May 2018 00:00:00.000 UTCG DSLWP_B.LunaInertial.SMA = 8761.0758581 DSLWP_B.LunaInertial.ECC = 0.768016853537 DSLWP_B.LunaInertial.INC = 16.9728174682 DSLWP_B.LunaInertial.RAAN = 295.670653562 DSLWP_B.LunaInertial.AOP = 130.427472407 DSLWP_B.LunaInertial.TA = 178.126596496 Final Covariance Matrix: 2.775051914396e-04 -3.147594758768e-07 -1.280429375311e-04 3.517602776169e-05 -5.883188691038e-05 1.160308310965e-04 -3.147594758757e-07 2.914272849542e-09 -4.306007194127e-07 -2.264526028570e-06 1.791213660556e-06 -4.665521352406e-08 -1.280429375317e-04 -4.306007194113e-07 8.153111702228e-04 1.646083472513e-03 -1.304473688036e-03 -1.176515569218e-04 3.517602775989e-05 -2.264526028572e-06 1.646083472515e-03 6.111684124351e-03 -5.032475787366e-03 -1.262607553387e-04 -5.883188690906e-05 1.791213660557e-06 -1.304473688037e-03 -5.032475787363e-03 4.201784367243e-03 8.933443876814e-05 1.160308310965e-04 -4.665521352482e-08 -1.176515569214e-04 -1.262607553373e-04 8.933443876712e-05 6.134160513576e-05 Final Correlation Matrix: 1.000000000000 -0.350008146122 -0.269189661768 0.027010385913 -0.054482915701 0.889324545140 -0.350008146121 1.000000000000 -0.279349533030 -0.536576640036 0.511876753799 -0.110346232089 -0.269189661770 -0.279349533029 1.000000000000 0.737411163707 -0.704785153348 -0.526088022721 0.027010385912 -0.536576640036 0.737411163708 1.000000000000 -0.993080292605 -0.206210305203 -0.054482915700 0.511876753799 -0.704785153349 -0.993080292605 1.000000000000 0.175964258943 0.889324545140 -0.110346232091 -0.526088022719 -0.206210305201 0.175964258941 1.000000000000

The normalized RMS error is about 3.5. This is higher than the desired value of 1, so it indicates that the orbit doesn’t fit our data so well, taking into account the error model we have provided. We will have to look at ways to improve data collection or data processing to reduce the error.

GMAT also produces the following plot for the residuals. Note that a residual of 0.028km/s of RangeRate corresponds to around 100Hz of residual in Doppler.

Let us now compare the orbit elements coming from the tracking file with the orbit elements produced by Doppler determination. For convenience, we list both again.

% State from tracking file DSLWP_B.SMA = 8765.409054517644; DSLWP_B.ECC = 0.7618824709853163; DSLWP_B.INC = 20.80912899224475; DSLWP_B.RAAN = 307.3706391221838; DSLWP_B.AOP = 118.7406568683716; DSLWP_B.TA = 178.2429103785479; % State from orbit determination DSLWP_B.SMA = 8761.0758581 DSLWP_B.ECC = 0.768016853537 DSLWP_B.INC = 16.9728174682 DSLWP_B.RAAN = 295.670653562 DSLWP_B.AOP = 130.427472407 DSLWP_B.TA = 178.126596496

We see that there is good agreement in the true anomaly. This is not a surprise, because it is easy to spot the time of periapsis in the Doppler data. The semi-major axis is 4.4km lower in the Doppler determination, implying a slightly faster orbit period. This is interesting because the 20180603 tracking file also shows a semi-major axis which is slightly lower than the other tracking files. I wonder if now that there are several days of measurements available, the DSLWP team at Harbin have used the measurements to refine the orbit period and this has shown up as a change in the 20180603 tracking file.

Other than that, the inclination, right-ascension of ascending element and argument of perigee show differences of a few degrees between the tracking file and the orbit determination. These parameters do not produce drastic changes in the Doppler curve, so it is not so easy to estimate them precisely.

Another thing that I wonder is to which extent is this estimation sensible in the 3400Hz shift I’ve applied to the Doppler data, since this shift has been estimated by eyesight.

After running the Doppler simulation again with the orbital elements obtained from the orbit determination, we get the following plot.

These calculations have been performed using this Jupyter notebook and this GMAT script.

It would be good to get more Doppler data from other stations and perhaps get the team at Harbin involved and share Doppler data and estimation techniques and results with them.

]]>Recall that there are three published tracking files that can be taken as a rough guideline of DSLWP-B’s actual trajectory. Each file covers 48 hours. The first file starts just after trans-lunar injection, and the second and third files already show the lunar orbit. Therefore, there is a gap in the story: how DSLWP-B reached the Moon.

There are at least two manoeuvres (or burns) needed to get from trans-lunar injection into lunar orbit. The first is a mid-course correction, whose goal is to correct slightly the path of the spacecraft to make it reach the desired point for lunar orbit injection, which is usually the lunar orbit periapsis (the periapsis is the lowest part of the elliptical orbit). The second is the lunar orbit injection, a braking manoeuvre to get the spacecraft into the desired lunar orbit and adjust the orbit apoapsis (the highest part of the orbit). Without a lunar orbit injection, the satellite simply swings by the Moon and doesn’t enter lunar orbit.

In this post we will see how to use GMAT to calculate and simulate these two burns, so as to obtain a full trajectory that is consistent with the published tracking files. The final trajectory can be seen in the figure below.

We have very few data (either from planning or from real measurements) regarding the burns performed by DSLWP-B. We know the following:

- The trajectory following trans-lunar injection
- The lunar elliptical orbit after lunar orbit injection
- A tentative date for the mid-course correction: 22 May 2018 22:55:00 UTC (but it seems that the correction was delayed)
- Some information by Nico Janssen PA0DLO regarding lunar orbit injection: “On 2018-05-25 between 14:00 and 15:00 UTC they are expected to pass the Moon at a distance of about 300 km. At that time they should perform a braking maneuver so that they can enter a high elliptical 300 x 9000 km orbit around the Moon.”
- The confirmation forwarded by PA0DLO that DSLWP-B achieved lunar orbit, sent at 16:25 UTC.

The rest of the details are still unknown. Therefore we will use our imagination and plan some manoeuvres that more or less agree with the available data and look plausible. If we learn further details we can always modify and refine our calculations.

The GMAT script I’ve used for this post is available in dslwp_injection.script. I have written it partly by hand, partly with help of the GUI. Here I will explain some of the more important sections of this script. It is recommended that you take a look at the target Mars B-plane tutorial to become familiar with the techniques I will use.

The idea of the calculations in this script goes as follows. We will use two elements for DSLWP-B: the start of the first tracking file (just after trans-lunar injection) and the start of the second tracking file (already in lunar orbit). We will perform a burn at 22 May 2018 22:55:00 to get at the appropriate time into the lunar periapsis given by the elliptical lunar orbit described by the second tracking file. Then we will perform a lunar orbit injection to match completely the elliptical lunar orbit. Note that orbit injections are usually done at periapsis because of the Oberth effect.

To do the required calculations, we will first propagate backwards in time the start of the second tracking file until the spacecraft reaches lunar periapsis, and we will take note of the epoch \(t_P\), position \(x_P\) and velocity \(v_P\) of the spacecraft. Then, we will take the start of the first tracking file and propagate forwards in time until the mid-course correction at 2018-05-22 22:55:00. We will then calculate a burn that makes the spacecraft reach the periapsis position as we have noted before (at the epoch \(t_P\), the position should be \(x_P\)). Finally, the lunar orbit injection burn is calculated to make the velocity of the spacecraft match \(v_P\).

In the GMAT script file, first we create the two spacecraft that we will use in the calculations. The first represents the start of the first tracking file and the second represents the start of the second file. We also include the mass of the satellite, which is known to be 45kg, and approximate values for the parameters used for solar radiation pressure and atmospheric drag calculations.

% Initial element from program_tracking_dslwp-a_20180520_window1.txt % DSLWP-B after trans-lunar injection Create Spacecraft DSLWP_B_TLI DSLWP_B_TLI.DateFormat = UTCModJulian DSLWP_B_TLI.Epoch = '28259.413090277776' DSLWP_B_TLI.CoordinateSystem = EarthFixed DSLWP_B_TLI.X = -6633.271172 DSLWP_B_TLI.Y = -802.293973 DSLWP_B_TLI.Z = -538.972574 DSLWP_B_TLI.VX = 0.029033 DSLWP_B_TLI.VY = -9.016021 DSLWP_B_TLI.VZ = -5.164087 DSLWP_B_TLI.DryMass = 45 DSLWP_B_TLI.Cd = 2.2 DSLWP_B_TLI.Cr = 1.8 DSLWP_B_TLI.DragArea = 0.25 DSLWP_B_TLI.SRPArea = 0.25 % Initial element from program_tracking_dslwp-b_20180526.txt % DSLWP-B in lunar orbit Create Spacecraft DSLWP_B_orbit DSLWP_B_orbit.DateFormat = UTCModJulian DSLWP_B_orbit.Epoch = '28264.5' DSLWP_B_orbit.CoordinateSystem = EarthFixed DSLWP_B_orbit.X = 303527.4363 DSLWP_B_orbit.Y = -255336.4971 DSLWP_B_orbit.Z = -38291.07068 DSLWP_B_orbit.VX = -17.836844 DSLWP_B_orbit.VY = -21.171093 DSLWP_B_orbit.VZ = -0.471039 DSLWP_B_orbit.DryMass = 45 DSLWP_B_orbit.Cd = 2.2 DSLWP_B_orbit.Cr = 1.8 DSLWP_B_orbit.DragArea = 0.25 DSLWP_B_orbit.SRPArea = 0.25

The GMAT mission script begins by setting the variable `correctionEpoch`

with the epoch for the mid-course correction. We also set an initial guess for the delta-v corresponding to this correction. This guess is actually the correct solution, which has been found before by running the script. At first, we knew nothing about the correct solution, so we set an initial guess of zero. The GMAT solver then finds the solution by starting with our guess and trying several values until it finds one that works. By storing that solution and using it as our initial guess, we save many computations when running the mission script again. It is an easy exercise for the reader to set the initial guess to zero again and see what happens.

BeginMissionSequence; BeginScript 'Init' correctionEpoch = 28261.45486111111 % 22 May 2018 22:55:00 UTC EndScript BeginScript 'Correction initial guesses' Correction.Element1 = 0.0135414468457 Correction.Element2 = -0.0278871540458 Correction.Element3 = -0.0145043816337 EndScript

As we have described above, we propagate the spacecraft in lunar orbit backwards in time until it reaches the periapsis. Then we propagate the spacecraft in trans-lunar injection forward in time until the mid-course correction epoch. The `PenUp`

and `PenDown`

commands are used to avoid discontinuities in the plots, since we are jumping in time.

% Propagate Luna orbit back to periapsis Propagate BackProp LunaProp(DSLWP_B_orbit) {DSLWP_B_orbit.Luna.Periapsis} % Propagate from Earth to mid-course correction PenUp EarthLunaView LunaOrbitView Propagate EarthProp(DSLWP_B_TLI) {DSLWP_B_TLI.ElapsedSecs = 60} % this is to avoid jumps in the plots PenDown EarthLunaView LunaOrbitView Propagate EarthProp(DSLWP_B_TLI) {DSLWP_B_TLI.UTCModJulian = correctionEpoch}

The next part of the mission script calculates the appropriate mid-course correction epoch using a DC (Differential Corrector) solver. Here we use the solver to try different values for the correction delta-v, then perform the manoeuvre and propagate until the epoch when the spacecraft should reach the lunar periapsis. We impose that at this moment the position of both satellites must coincide.

Target 'Target lunar periapsis' DC % Vary mid-course correction parameters Vary DC(Correction.Element1 = Correction.Element1, {Perturbation = 0.0001, MaxStep = 0.005}) Vary DC(Correction.Element2 = Correction.Element2, {Perturbation = 0.0001, MaxStep = 0.005}) Vary DC(Correction.Element3 = Correction.Element3, {Perturbation = 0.0001, MaxStep = 0.005}) % Perform mid-course correction Maneuver 'Mid-course correction' Correction(DSLWP_B_TLI) % Propagate to epoch of lunar periapsis Propagate LunaProp(DSLWP_B_TLI) {DSLWP_B_TLI.UTCModJulian = DSLWP_B_orbit.UTCModJulian} % Impose conditions of arrival to periapsis Achieve DC(DSLWP_B_TLI.X = DSLWP_B_orbit.X, {Tolerance = 0.001}) Achieve DC(DSLWP_B_TLI.Y = DSLWP_B_orbit.Y, {Tolerance = 0.001}) Achieve DC(DSLWP_B_TLI.Z = DSLWP_B_orbit.Z, {Tolerance = 0.001}) EndTarget

When we have found the appropriate mid-course correction, we write the parameters to a report file for later analysis. Then, we compute the lunar orbit injection delta-v. Since the condition for this burn is that both spacecrafts should end up with the same velocity vector, the delta-v is straightforward to compute as the difference in velocities. Here we are using the `DSLWP_B_TLI_VBN`

reference frame, which is a V (velocity), N (normal), B (binormal) reference frame to be explained below. We are only using this frame to interpret the results more easily. Any other reference frame would be adequate.

Once the delta-v has been computed, we perform the manoeuvre, write the parameters to the report file and then propagate for 2 days to see that the orbit we have achieved really matches the orbit described in the second tracking file.

Report 'Report mid-course correction burn' burnsReport correctionEpoch Correction.Element1 Correction.Element2 Correction.Element3 BeginScript 'Setup LOI' LOI.Element1 = DSLWP_B_orbit.DSLWP_B_TLI_VNB.VX - DSLWP_B_TLI.DSLWP_B_TLI_VNB.VX LOI.Element2 = DSLWP_B_orbit.DSLWP_B_TLI_VNB.VY - DSLWP_B_TLI.DSLWP_B_TLI_VNB.VY LOI.Element3 = DSLWP_B_orbit.DSLWP_B_TLI_VNB.VZ - DSLWP_B_TLI.DSLWP_B_TLI_VNB.VZ EndScript Maneuver 'Lunar orbit injection 'LOI(DSLWP_B_TLI) Report 'Report LOI burn' burnsReport DSLWP_B_TLI.UTCModJulian LOI.Element1 LOI.Element2 LOI.Element3 Propagate LunaProp(DSLWP_B_TLI) {DSLWP_B_TLI.ElapsedDays = 2}

Below we can see the final trajectory in an ECI reference frame.

The VNB (velocity-normal-binormal) frame is a very usual reference frame to plan orbital manoeuvres. It is centred on the spacecraft and its three coordinate vectors point as follows: the V vector points along the velocity vector, tangent to the orbit; the N vector is perpendicular to V and contained in the orbital plane; the B vector completes a right-handed coordinate system. Burns along the V vector increase or decrease the orbital speed, changing the eccentricity and semi-major axis, and burns along the B vector change the orbital plane. Therefore, it is easy to understand what is the effect of a particular burn in terms of its expression in the VNB frame. For that reason, we have used VNB frames for both burns.

For the lunar orbit injection burn, we need to do calculations (compute the velocity vector of both spacecraft) in the VNB frame of `DSLWP_B_TLI`

, so we need to declare explicitly this reference frame as follows.

Create CoordinateSystem DSLWP_B_TLI_VNB DSLWP_B_TLI_VNB.Origin = DSLWP_B_TLI DSLWP_B_TLI_VNB.Axes = ObjectReferenced DSLWP_B_TLI_VNB.XAxis = V DSLWP_B_TLI_VNB.YAxis = N DSLWP_B_TLI_VNB.Primary = Luna DSLWP_B_TLI_VNB.Secondary = DSLWP_B_TLI

After running the script, the contents of the burns report file (stored in `output/burnsReport.txt`

) is the following:

correctionEpoch Correction.Element1 Correction.Element2 Correction.Element3 28261.45486111111 0.0135414468457 -0.0278871540458 -0.0145043816337 DSLWP_B_TLI.UTCModJulian LOI.Element1 LOI.Element2 LOI.Element3 28264.16954021541 -0.2951585933116597 0.001252079927705898 0.04183219943959633

The units of the burn elements are km/s. We see that the mid-course correction is a small burn, with a total delta-v of 34.2 m/s. The lunar-orbit injection is a much larger burn, with a total delta-v of 298.1 m/s. Most of the burn happens in the retrograde (-V) direction. This is because the spacecraft is braked to enter lunar orbit.

The epoch of the lunar orbit insertion, which coincides with the lunar periapsis immediately before 2018-05-26 00:00:00 UTC, is 2018-05-25 16:04:08 UTC. This seems a little late, given the reports by Nico that lunar orbit injection would be around 14:00 or 15:00, and the confirmation of successful orbit injection at 16:25.

The altitude of the periapsis can be seen by using the “Command summary” feature of GMAT, just after the lunar orbit injection burn. It is 368.27km, which matches more or less Nico’s information. Other interesting orbital parameters (in the lunar inertial reference frame) are as follows.

- Orbit period: 73455s (20h 24min 15s)
- Semi-major axis: 8750.7km
- Eccentricity: 0.759
- Inclination: 20.8º
- Right-ascension of ascending node: 308.2º
- Argument of perigee: 118º
- Altitude of apoapsis: 13655km

The burns we have performed in GMAT are impulsive burns, where the velocity is changed instantly. This is not realistic but makes burn planning easier. Some knowledge of the propulsion system of DSLWP-B would enable us to use finite burns, which simulate the finite amount of thrust that is available. Fuel consumption is also modelled.

In this post we have seen how to imagine and plan some plausible manoeuvres with the little data we have available. Additional knowledge would enable us to constrain our freedom and simulate a more realistic trajectory. We would need to have at least some of the following data.

Data regarding the burns:

- Epoch of the mid-course correction
- Delta-v of mid-course correction (as a vector, or total delta-v, or just Doppler change, which represents the projection of the delta-v vector onto the line of sight)
- Epoch of the lunar orbit injection
- Delta-v of the lunar orbit injection
- Whether the lunar orbit injection happened right at periapsis or somewhere near

Data regarding the thruster for DSLWP-B. Among other parameters, GMAT allows to use the following:

- Geometry of the thruster with respect to the spacecraft’s body (thrust direction)
- Thrust and specific impulse, depending on the tank pressure and temperature (see here for details)

Data regarding the fuel tanks for DSLWP-B. GMAT allows us to model the following parameters of a chemical tank:

- Fuel mass
- Fuel density (some estimate using the type of fuel would suffice)
- Temperature and reference temperature (probably not so important)
- Pressure in the fuel tank
- Volume of the fuel tank
- Pressure model: pressure regulated or blow down

With a little luck, we will be able to get some of the data (or at least ballpark estimates) from the DSLWP team and use it to refine our simulations.

]]>This launch was shared by the DSLWP-A and -B microsatellites, also called Longjiang 1 and 2. These two satellites are designed to be put on a 200 x 9000km lunar orbit and their main scientific mission is a proof of concept of the Discovering the Sky at Longest Wavelengths experiment, a radioastronomy HF interferometer that uses the Moon as a shield from Earth’s interferences.

The DSLWP satellites carry an Amateur radio payload which consists of a 250 baud (or 500 baud) GMSK transmitter which uses \(r=1/2\) or \(r=1/4\) turbo codes, a JT4G beacon, and a camera allowing open telecommand (such as the camera on BY70-1 and LilacSat-1). A year ago, while the radio system was being designed, I wrote a post about DSLWP’s SSDV downlink, which transmits the images taken by the camera.

Wei Mingchuan BG2BHC, who is part of the DSLWP team, has been posting updates on Twitter about the status of the mission. If you’ve been following these closely, you’ll already know that unfortunately radio contact with DSLWP-A was lost on the UTC afternoon of May 22. Since then, all tries to contact the spacecraft have failed (the team will publicly release more information about its fate soon). On the other hand, DSLWP-B has been successfully injected into lunar orbit and is now orbiting the Moon since the UTC afternoon of May 25.

More posts will follow about the radio communications of DSLWP, but this series of posts will deal with the orbital dynamics part of the mission. In this first post, I will look at the tracking files released so far by Wei, which can be used to compute the spacecraft’s position and Doppler.

The main tool I will be using is GMAT R2018a, which is an open-source tool designed to plan, analyse and model spacecraft trajectories. It is a very comprehensive tool developed by NASA and other partners. I have been introduced to GMAT by Nico Janssen PA0DLO, who has proposed it for tracking of Amateur deep space missions. See Nico’s page for some useful resources regarding GMAT and deep space tracking, including some scripts for DSLWP.

While I know the basics of orbital dynamics (dating back to my years of playing Orbiter), I’m far from an expert on the field, and I had never used GMAT before, so I’m learning as I go. I hope that these series of posts can help others to get introduced to this very interesting topic.

GMAT has a very nice series of tutorials, which get you from the very basics to rather complex topics. I suggest you to go at some point through the first tutorial to get a hang of GMAT’s workflow and general concepts.

So far, three tracking files have been released by Wei, the first one is included in gr-dslwp, the GNU Radio decoder for the GMSK telemetry, and was released on May 21. The second and third have been released today.

These tracking files are plain text files which contain a listing of the spacecraft’s position and velocity at specific moments in time. Each line contains the data for a different instant. There are seven columns in each line. The first column contains the date and time in Unix timestamp format (seconds since January 1 1970). The three next columns contain the position in ECEF coordinates. The units are km. The last three columns contain the velocity in ECEF coordinates, in units of km/s. There is a line for each second (the timestamp increases one second per line).

ECEF coordinates are a cartesian reference system with origin on the Earth’s centre and which is “fixed” to Earth, so it rotates together with Earth’s rotation. In this manner, points fixed on the Earth’s surface have constant ECEF coordinates (i.e., their coordinates do not vary with time, as Earth rotates), so it is possible to pass from latitude and longitude to ECEF. The Z axis of ECEF coordinates points to the north pole, the X axis points to the point with latitude 0º and longitude 0º, and the Y axis is chosen to form a right-handed system.

There is a different type of coordinates, which are known as ECI or inertial coordinates. Their origin is also the Earth’s centre, but they do not rotate with the Earth. The Z axis of ECI coordinates also points to the north pole, but the X axis points to the vernal equinox (the intersection of Earth’s ecliptic and equatorial planes, in the direction of Aries).

In many applications, ECI coordinates are more useful than ECEF. For instance, in the case of DSLWP, ECI coordinates can be easier to interpret. In ECEF coordinates it seems that DSLWP orbits the Earth roughly one time per day, while in reality it is the Earth the thing that rotates. On the other hand, ECEF coordinates simplify somewhat the calculation of azimuth, elevation, distance to the spacecraft and Doppler for an observer on a fixed point on Earth, so probably that’s the reason why DSLWP tracking files are given in ECEF coordinates, since they are used in the GNU Radio decoder for that purpose. ECI coordinates are also useful because Newton’s laws can only be applied in an inertial reference frame.

Now that we’ve understood DSLWP tracking files, the main goal of this post is to interpret and validate them using GMAT. If you’ve already taken a look at the first tutorial, you’ll have seen that GMAT’s most basic functionality is trajectory propagation. Essentially, you give a spacecraft’s orbital elements, which are its position and velocity at a particular instant in time (for instance using ECEF or ECI coordinates, but other ways are possible) and tell GMAT to propagate the trajectory of the spacecraft for a certain time. GMAT will calculate the forces acting on the spacecraft (mainly gravity, but also others such as solar radiation pressure and atmospheric drag) and will update and propagate the position of the spacecraft by using Newton’s second law.

Thus, we will take each of the tracking files and use the first listed position and velocity as the spacecraft’s orbital elements in GMAT. Then we will use GMAT to propagate the trajectory of the spacecraft and compute its position every second. Finally we compare GMAT’s results with the tracking file. To do so, we can use GMAT to generate a file similar to the tracking file, with the position and velocity in ECEF coordinates. The only difference is that GMAT doesn’t use the Unix timestamp format, so we output time in UTC Modified Julian Days (note that GMAT’s MJD convention is different from the usual MJD convention). To compare GMAT’s propagation with the tracking file, we use Python to calculate and the position error (the distance between the position as predicted by GMAT and the position given by the tracking file).

This serves two purposes. First, we can validate that the published tracking files do not contain any planned manoeuvres, where the spacecraft fires its thrusters to change course (in which case we would also need to inform GMAT of those manoeuvres). Second, we can compare the force models used by GMAT with those used to compute the tracking files. As we shall see, there are many subtle details regarding the forces acting on a spacecraft.

There are two ways of running calculations with GMAT. The first one is to create a

Since I’m going to process three tracking files (and perhaps more that get published in the future), I have wanted to automate the procedure as much as possible. Therefore, I use Python to read the tracking file and create a GMAT script file using some data read from the tracking file (the spacecraft’s orbital elements, for instance). While this can seem hard, it is actually not that difficult. I have first created a mission using the GUI and then have used the corresponding script file as a template.

The Python code used in this post can be found in this Jupyter notebook.

The first tracking file starts at 20 May 21:54:51 UTC and last 48 hours. It contains data from shortly after trans-lunar injection to before mid-course correction, which was programmed at 22 May 22:55 UTC, but was presumably delayed. The image below shows the orbit as propagated by GMAT (click on the image to view it in full size). Note that this orbit is plotted using ECI coordinates. The orbit in ECEF coordinates would look rather weird.

Below we can see the error between GMAT’s propagation and the tracking file. We see that it is small. This error is due to different modelling of the forces acting on the spacecraft. In GMAT I have used a very detailed model (probably an overkill) which simulates Earth’s non-spherical gravity using spherical harmonics up to degree and order 10 (see below for a discussion about spherical harmonics), point mass gravities for the Sun, Moon, and all the planets except for Mercury and Pluto (which are very small and distant), solar radiation pressure, relativistic effects, and Earth’s atmosphere (even though it is of little significance, since the spacecraft gets away from the atmosphere really soon).

The second tracking file starts at 26 May 00:00:00 UTC and lasts 48 hours. It contains data after DSLWP-B was established on a lunar elliptical orbit.

Below we can see the error between GMAT’s propagation and the tracking file. Again, I have used a very detailed force model in GMAT. The model is is the same as before, but using the Moon’s spherical harmonics instead of those of Earth’s, since now the Moon is the nearest object. Also Earth’s atmosphere is not included now.

Now we see that the error is rather large, peaking up to 50km at times. These peaks corresponds to the times when DSLWP-B passes the periapsis (the point of the orbit which is nearest to the Moon). Recall that an object on an elliptical orbit moves faster at the periapsis and slower at the apoapsis (the point of the orbit which is farthest from the Moon), because of Kepler’s second law. Since DSLWP-B is on a highly elliptical orbit, it passes the periapsis very quickly and spends most of its time near the apoapsis.

Therefore, if there is a small difference between the orbital calculation on GMAT and in the calculations used to obtain the tracking file, that difference will cause DSLWP-B to pass the periapsis at slightly different times. This causes a large error in the position of the spacecraft when passing near the periapsis.

Another effect that we see in the graph is that there is an error that accumulates with every orbit. We will study this in more detail later.

The third tracking file starts at 28 May 00:00:00 UTC and lasts 48 hours. It is just a continuation of the data in the second tracking file.

The figure below shows the position error. We have used the same force model in GMAT as for the second tracking file. We see the same behaviour as before.

The large errors seen in the second and third tracking files seem to indicate that a different force model has been for the calculation of the tracking files. To investigate this, I have run again the second file in GMAT using a simplified model that only includes the gravity of the Moon with a variable number of spherical harmonics and the gravity of Earth as a point mass.

Without going into much detail about spherical harmonics, I can say the following. An object which is a perfect sphere generates the same gravitational field as a point mass (meaning a mass concentrated at a single point). This gravitational field is symmetric and only depends on the distance to the object, which is what Kepler’s laws and all other simple theories of orbital mechanics assume. However, planets and moons are not perfectly spherical, and also they do not have a uniform density. Therefore, their gravitational field deviates from the ideal symmetric field. At a large distance, all these deviations somehow even out and the gravitational field seems almost perfect. However, at shorter distances, the effects of these deviations can be more significant.

Spherical harmonic coefficients are a mathematical way to measure these deviations. They are numbers with an associated degree and order. The coefficients with the lower order play the largest role, while the effect of coefficients of higher order is very mild and can usually be ignored. Hundreds or thousands of spherical harmonic coefficients have been measured for the Earth’s gravitational field and other astronomic bodies, including the Moon. The most important coefficient is the one having degree 2 and order 0. This coefficient describes the oblateness of the body, and it is the coefficient that plays the largest role in the non-spheric gravitational field.

It has been necessary to include the Earth in this simple model, since otherwise the error quickly grows and becomes very large, due to the fact that the Earth’s gravity plays a very important role in lunar orbits. This is not surprising since the Earth is a rather massive and near object. Checking this with a simulation in GMAT is left as an exercise for the reader.

Below we can see the errors obtained using models with a different number of spherical harmonics for the Moon’s gravity, and also the error obtained above using the complete model.

The error is much lower when no spherical harmonics are considered. This indicates that the calculations in the tracking files haven’t even taken the Moon’s oblateness into account. This will give an error in the tracking files that accumulates with each orbit.

The error when passing the periapsis is large in all the models I’ve used. I do not know what is the reason for this. Probably there is some peculiarity about the model used for the tracking files that I haven’t considered. It would be interesting to know how they are calculated. Wei tells me that the team uses STK for orbit calculations.

**Update:** People interested in using the GMAT output reports generated by this Jupyter notebook as tracking files for gr-dslwp can use the following Python script. It reads a GMAT report from the standard input and writes the corresponding tracking file to the standard output.

Just after reading Lorenzo’s description of the coding, where he mentions Golay and Reed Solomon, I noticed that 1KUNS-PF was using the NanoCom AX100 transceiver in ASM+Golay mode. This is the same mode that the Chinese TY-2 and TY-6 satellites use, and I’ve already spoken about ASM+Golay mode in a post about TY-2. The only difference between these Chinese satellites and 1KUNS-PF is that 1KUNS-PF is currently using 1k2 (but perhaps might change to 9k6 in the future), whereas the Chinese satellites use 9k6. With this in mind, it is very easy to adapt the decoder for TY-2 to obtain a decoder for 1KUNS-PF.

Regarding my previous tries, note that I had tried to identify the syncword as `11011001111010010101110001000011`

, whereas the correct syncword is `10010011000010110101000111011110`

. My syncword candidate was inverted (this might be a problem with sideband inversion in the recording by Mike that I used) and off by one bit (due to the difficulty of deciding where the preamble ends).

After reading Lorenzo’s email, it has been a very easy and fast task to add a fully working decoder to gr-satellites, while before I wasn’t optimistic at all about the difficulty of decoding this satellite. This makes me think about two things:

- We should really check the usual suspects (i.e., popular modems) when trying to reverse-engineer some new satellite. I could have found this out by myself just by trying the AX100 ASM+Golay decoder.
- Some advice IARU Satcoord: if a satellite uses some popular hardware (for instance the U482C or the AX100) or some popular standard (CCSDS), please list that in the frequency coordination sheet. Lorenzo’s email could have been well summarised in the sentence “1KUNS-PF uses a NanoCom AX100 in the ASM+Golay mode”, and then we would have been able to decode this satellite without any effort.

Lorenzo has also sent us the telemetry format, which is rather simple. Using that, I’ve been able to add a telemetry decoder also. The new decoder for 1KUNS-PF can be found as `sat_1kuns_pf.grc`

in gr-satellites. I have also added a sample recording to satellite-recordings. The telemetry in one of the packets in the sample recording is as follows:

CSP header: Priority: 2 Source: 1 Destination: 9 Destination port: 10 Source port: 37 Reserved field: 0 HMAC: 0 XTEA: 0 RDP: 0 CRC: 0 Container: beacon_counter = 4274 solar_panel_voltage = ListContainer: 2448.0 2448.0 2432.0 eps_temp = ListContainer: 1.0 3.0 2.0 2.0 eps_boot_cause = 7 eps_batt_mode = 3 solar_panel_current = 0.0 system_input_current = 80.0 battery_voltage = 8262.0 radio_PA_temp = 4.0 tx_count = 45584 rx_count = 0 obc_temp = ListContainer: 1.0 1.0 ang_velocity_mag = 10 magnetometer = ListContainer: 288.0 0.0 0.0 main_axis_of_rot = 89]]>