The image accompanying this post has a nice story to it. It was taken by the Amateur camera in DSLWP-B, the Chinese microsatellite in lunar orbit. On February 27, a download of this image was attempted by transmitting the image in SSDV format in the 70cm band and receiving it in the Dwingeloo radiotelescope, in the Netherlands.
The download was attempted twice, but due to errors in the transmission, a small piece of the image was still missing. Today, the Amateur payload of DSLWP-B was active again, and the plan was to download the missing piece, as well as other images. However, after the payload turned on and transmitted its first telemetry beacons, we discovered that the image had been overwritten.
The camera on-board DSLWP-B has a buffer that stores the last 16 images taken. Any of these images can be selected to be transmitted (completely or partially) while the Amateur payload is active. An image can be taken manually by issuing a command from ground. Besides this, every time the Amateur payload powers on, an image is taken. Of course, taking new images overwrites the older ones.
This is what happened today. The image we wanted to download was the oldest one in the buffer and got overwritten as soon as the payload turned on. This is a pity, especially because there was another activation of the payload last Friday, but a large storm in Germany prevented Reinhard Kuehn DK5LA’ from moving his antennas safely, so the satellite couldn’t be commanded to start the download.
After seeing that the image had been overwritten, Tammo Jan Dijkema suggested that I try to recover manually the missing chunk in the recording made on February 27. As you can see, I was successful. This is a report of how I proceeded.
First I downloaded the IQ recording and SSDV data from the CAMRAS DSLWP data server. By running the ssdv_sort.py script with the SSDV data recorded in Dwingeloo, we get the following.
$ ~/jupyter_notebooks/dslwp/ssdv_sort.py DSLWP-B_PI9CAM_2019-02-27T07_27_27.bin /tmp/camras
Calling SSDV decoder for image 0xb0
Callsign: DSLWP
Image ID: B0
Resolution: 640x480
MCU blocks: 2400
Sampling factor: 2x1
Quality level: 5
Gap detected between packets -1 and 1
Gap detected between packets 17 and 19
Gap detected between packets 31 and 33
Gap detected between packets 68 and 70
Read 70 packets
Calling SSDV decoder for image 0xab
Callsign: DSLWP
Image ID: AB
Resolution: 640x480
MCU blocks: 2400
Sampling factor: 2x1
Quality level: 5
Packets are not in order. 1 > 1
Packets are not in order. 1 > 1
Packets are not in order. 2 > 2
Packets are not in order. 2 > 2
Gap detected between packets 2 and 4
Packets are not in order. 4 > 4
Packets are not in order. 4 > 4
Packets are not in order. 5 > 5
Packets are not in order. 7 > 7
Packets are not in order. 8 > 8
Packets are not in order. 9 > 9
Packets are not in order. 10 > 10
Packets are not in order. 10 > 10
Packets are not in order. 14 > 14
Packets are not in order. 15 > 15
Packets are not in order. 16 > 16
Packets are not in order. 17 > 17
Packets are not in order. 19 > 19
Packets are not in order. 20 > 20
Packets are not in order. 21 > 21
Packets are not in order. 22 > 22
Packets are not in order. 23 > 23
Packets are not in order. 24 > 24
Packets are not in order. 25 > 25
Packets are not in order. 26 > 26
Packets are not in order. 28 > 28
Packets are not in order. 29 > 29
Packets are not in order. 31 > 31
Packets are not in order. 32 > 32
Packets are not in order. 33 > 33
Packets are not in order. 34 > 34
Packets are not in order. 35 > 35
Packets are not in order. 36 > 36
Packets are not in order. 37 > 37
Packets are not in order. 38 > 38
Packets are not in order. 39 > 39
Packets are not in order. 40 > 40
Packets are not in order. 41 > 41
Packets are not in order. 45 > 45
Packets are not in order. 48 > 48
Packets are not in order. 49 > 49
Packets are not in order. 51 > 51
Packets are not in order. 52 > 52
Packets are not in order. 53 > 53
Packets are not in order. 54 > 54
Packets are not in order. 55 > 55
Packets are not in order. 56 > 56
Packets are not in order. 57 > 57
Packets are not in order. 58 > 58
Packets are not in order. 60 > 60
Packets are not in order. 61 > 61
Packets are not in order. 62 > 62
Packets are not in order. 63 > 63
Packets are not in order. 64 > 64
Packets are not in order. 65 > 65
Packets are not in order. 68 > 68
Packets are not in order. 69 > 69
Gap detected between packets 69 and 71
Read 126 packets
We see that two images, 0xb0
and 0xab
are present in the data. The image we are interested in is 0xab
(or 171), which has most of its packets duplicated (hence the “packets are not in order” messages) because it was transmitted twice completely. However, we see that the decoder detects some gaps, meaning that the following chunks are missing: 3 and 70. The decoded image is shown below.
Chunk 70 is at the bottom of the image, and we don’t care about it, because it is completely black. However, the missing chunk 3 is what causes the purple strip at the top of the image. This is the chunk that we intended to download again today.
Next, I play back the IQ recording with my GNU Radio decoder flowgraph and use inspectrum to view the waterfall of the recording. I try to find in what parts of the recording chunk 3 is transmitted and what is the problem that prevents the decoder from getting this chunk intact. Maybe I’m lucky and it is just a matter of tuning parameters such as the PLL bandwidth.
However, we are used to seeing corrupted frames caused by jumps in the TCXO on-board DSLWP, so I expect to find this kind of problem. Fortunately, I had already thought a solution for this problem, which is the reason why Tammo suggested me to fix this image. The problem with the frequency jump suffered by the TCXO is that it upsets the OQPSK receiver PLL, so it loses phase lock. Instead of reading the GMSK signal as OQPSK, we can read it as FSK. This has the advantage that the small frequency jump is barely noticeable, so we can read off the symbols from the FSK demodulation without any problems.
This is not the end of the story, though. The GMSK signal is precoded to allow it to be read off as OQPSK directly (you can read an in-depth discussion about precoding in this post). If we read the signal as FSK, we need to undo this precoding. The bad news is that the precoder is a differential encoder. By this, I mean something that transmits \(y_n = x_n – x_{n-1}\), not the usual encoder that you would use together with a differential decoder. This usual encoder I call integral encoder, because it transmits \(y_n = \sum_{k = -\infty}^n x_k\) (and then the differential decoder undoes the integral). Analogously, the decoder for a differential encoder is an integral decoder, which recovers \(x_n = \sum_{k=-\infty}^n y_k\) (note that this is a telescoping sum).
The unpleasant thing about integral decoders is that if one of the received \(y_n\) has a bit error, then the whole recovered sequence will be wrong from that point on. For this reason, no one uses differential encoders/integral decoders in a real communications system. In contrast, if we deal with a differential decoder, and one received \(y_n\) is wrong, then only \(x_n\) and \(x_{n+1}\) will be recovered incorrectly. For this reason, integral encoders/differential decoders are used in real communications systems.
The thing that saves us is that the SNR with which the large radiotelescope at Dwingeloo receives the signal from DSLWP-B is so large that we usually don’t get any bit errors in the FSK symbols. Therefore, we can run our integral decoder to undo the precoder. Even one bit error in the FSK symbols would cause bit errors from that point onwards in the frame, preventing its correct decoding, despite the use of a Turbo code.
The complete signal processing can be seen in this Jupyter notebook. We start with a segment of the IQ recording containing a transmission of chunk 3, and we end up with the Turbo codewords transmitted in that segment. For simplicity, the Turbo codewords are extracted using hard symbols.
It is convenient to remark that, from the two times that chunk 3 is transmitted in the recording, I have used the second one. When analyzing the first one, I found out that the symbol clock of the GMSK signal seemed to jump. This most likely indicates that some IQ samples were lost in the receiver when doing the recording. This problem is difficult to fix, so I used the second transmission, which just has a clock jump in the transmitter TCXO.
The Turbo codewords obtained in the Jupyter notebook are stored in a file. Then they are read with this GNU Radio flowgraph, which contains the last half of a DSLWP-B decoder (from the Turbo decoding onwards). The flowgraph performs Turbo decoding and telemetry parsing, outputting the SSDV frames to a file called dslwp_ssdv_recording_fix.bin
, which can be obtained in this gist.
After running this flowgraph, using cat
we can concatenate the SSDV data received at Dwingeloo with the data that was decoded manually using FSK demodulation. Then we can run ssdv_sort.py
again.
$ cat ~/Descargas/DSLWP-B_PI9CAM_2019-02-27T07_27_27.bin /tmp/dslwp_ssdv_recording_fix.bin > /tmp/fixed.bin
$ ~/jupyter_notebooks/dslwp/ssdv_sort.py /tmp/fixed.bin /tmp/camras_fixed
Calling SSDV decoder for image 0xb0
Callsign: DSLWP
Image ID: B0
Resolution: 640x480
MCU blocks: 2400
Sampling factor: 2x1
Quality level: 5
Gap detected between packets -1 and 1
Gap detected between packets 17 and 19
Gap detected between packets 31 and 33
Gap detected between packets 68 and 70
Read 70 packets
Calling SSDV decoder for image 0xab
Callsign: DSLWP
Image ID: AB
Resolution: 640x480
MCU blocks: 2400
Sampling factor: 2x1
Quality level: 5
Packets are not in order. 1 > 1
Packets are not in order. 1 > 1
Packets are not in order. 1 > 1
Packets are not in order. 2 > 2
Packets are not in order. 2 > 2
Packets are not in order. 2 > 2
Packets are not in order. 4 > 4
Packets are not in order. 4 > 4
Packets are not in order. 4 > 4
Packets are not in order. 5 > 5
Packets are not in order. 5 > 5
Packets are not in order. 6 > 6
Packets are not in order. 7 > 7
Packets are not in order. 7 > 7
Packets are not in order. 8 > 8
Packets are not in order. 9 > 9
Packets are not in order. 10 > 10
Packets are not in order. 10 > 10
Packets are not in order. 14 > 14
Packets are not in order. 15 > 15
Packets are not in order. 16 > 16
Packets are not in order. 17 > 17
Packets are not in order. 19 > 19
Packets are not in order. 20 > 20
Packets are not in order. 21 > 21
Packets are not in order. 22 > 22
Packets are not in order. 23 > 23
Packets are not in order. 24 > 24
Packets are not in order. 25 > 25
Packets are not in order. 26 > 26
Packets are not in order. 28 > 28
Packets are not in order. 29 > 29
Packets are not in order. 31 > 31
Packets are not in order. 32 > 32
Packets are not in order. 33 > 33
Packets are not in order. 34 > 34
Packets are not in order. 35 > 35
Packets are not in order. 36 > 36
Packets are not in order. 37 > 37
Packets are not in order. 38 > 38
Packets are not in order. 39 > 39
Packets are not in order. 40 > 40
Packets are not in order. 41 > 41
Packets are not in order. 45 > 45
Packets are not in order. 48 > 48
Packets are not in order. 49 > 49
Packets are not in order. 51 > 51
Packets are not in order. 52 > 52
Packets are not in order. 53 > 53
Packets are not in order. 54 > 54
Packets are not in order. 55 > 55
Packets are not in order. 56 > 56
Packets are not in order. 57 > 57
Packets are not in order. 58 > 58
Packets are not in order. 60 > 60
Packets are not in order. 61 > 61
Packets are not in order. 62 > 62
Packets are not in order. 63 > 63
Packets are not in order. 64 > 64
Packets are not in order. 65 > 65
Packets are not in order. 68 > 68
Packets are not in order. 69 > 69
Gap detected between packets 69 and 71
Read 133 packets
We see that chunk 3 is no longer missing from image 0xab
. The SSDV decoder now produces the following image, which is also shown at the top of this post.