Capture Satellite Data Using AWS Ground Station and Decode IQ Samples Using CCSDS TM Receiver
This example shows how to use the Amazon® Web Services (AWS) Ground Station service from within MATLAB® to receive data from Earth observation satellite AQUA. AQUA (launched in 2002), is an Earth-orbiting, National Aeronautics and Space Administration (NASA), scientific research satellite that studies precipitation, evaporation, and cycling of water. You can capture data from other satellites for which you have access permission by changing the satellite information. Using this example, you can capture satellite data as radio frequency (RF) in-phase quadrature (I/Q) samples, and then demodulate and decode the samples. You can further process and analyze this captured data using a Consultative Committee for Space Data Systems (CCSDS) Telemetry (TM) receiver.
Introduction
AWS Ground Station is a service that enables you to manage satellite communications and process data without the need to build or maintain your own ground station infrastructure. You can get more information about the AWS Ground Station service and its capabilities from the AWS Ground Station website.
AWS Ground Station currently supports low earth orbit (LEO) and medium earth orbit (MEO) satellites. These satellites are visible from the ground station for only a few minutes during each pass, due to their orbital cycles. Communication is possible when the satellites are within the line of sight of a ground station. AWS Ground Station establishes contact with the satellites, and then receives, demodulates, and decodes RF signals from them. The AWS ground station delivers your contact data asynchronously to an Amazon Simple Storage Service (S3) bucket associated with your account. The service delivers your contact data as packet capture (PCAP) files. You can replay the contact data into a software-defined radio (SDR) or extract the payload data from the PCAP files for processing.
AWS Services and Costs
This example uses these AWS services, some of which can incur costs on your AWS account. For cost estimates, see the linked pricing page for each AWS service.
AWS enables you to visualize, understand, and manage your AWS costs and usage over time. For more details, see the AWS Cost Explorer website.
Set Up Access to AWS
To capture data from satellites using the AWS Ground Station service, you must have an AWS account with permission to use the AWS services this example uses. For more information, see the Setting Up AWS Ground Station website. To set up access to the AWS services used in this example, follow these steps.
Create an Identity and Access Management (IAM) user with programmatic access, and permission to use all the AWS service policies listed in the AWS Services and Costs section. For more information on creating an IAM user, see Creating IAM users (console) in the AWS documentation.
Once you have created an IAM user, download the
.csv
credentials file to configure the AWS command line interface (CLI) for use with MATLAB.
Request Access to Ground Station
To get access to ground station services on your account, email aws-groundstation@amazon.com with the satellite ID and your AWS account ID. This figure shows a sample email for obtaining access to AWS services.
Configure AWS CLI
In this example, you configure the AWS CLI for accessing the AWS services by following these steps.
Download and install the latest AWS CLI from the AWS Command Line Interface site.
Configure the installed AWS CLI using the previously downloaded
.csv
file. To instead configure the AWS CLI outside of MATLAB, use the AWS IAM Identity center.
If you do not have an IAM username or credentials saved in a .csv
file, see the Set Up Access to AWS section. Contact your administrator, see how to sign in to your AWS account and configuring AWS CLI to use IAM Identity Center.
Once you have a .csv
credentials file, uncomment these commands and replace the fullfile
input arguments with the full path to the file on your computer.
% csvCredentialsFile = fullfile("C:","Work","credentials.csv"); % cfg = HelperAWSAccount(csvCredentialsFile);
If you use IAM Identity Center to configure the AWS CLI, replace the profileName
value with your profile name. A profile comprises the settings and credentials used to execute the AWS commands.
profileName = "JohnDoe";
cfg = HelperAWSAccount(profileName)
cfg = HelperAWSAccount with properties: ProfileName: "JohnDoe" CSVFile: []
Get Satellites List
To obtain the list of satellites accessible in your AWS account, use the HelperSatellitelist
function.
satelliteList = HelperSatellitelist(cfg);
satelliteList(:,(1:3)) % Display satellite list
ans=6×3 table
SatelliteName SatelliteID GroundStation
_____________ ___________ _____________
"AQUA" 27424 "Ohio 1"
"AQUA" 27424 "Oregon 1"
"NOAA20" 43013 "Ohio 1"
"NOAA20" 43013 "Oregon 1"
"SUOMINPP" 37849 "Ohio 1"
"SUOMINPP" 37849 "Oregon 1"
Capture Satellite Data
In this example, you capture satellite data by inputting satellite ID and ground station location into the HelperSatelliteDataCapture
function. You can input this information manually, or you can input a row from the table in satelliteList
.
% (optional) If the S3 Bucket you are using for data storage is in the % same region as ground station, specify its name. s3Bucket = ""; % (optional) Specify an email address at which to receive updates % regarding the status of a scheduled contact. notificationEmail = ""; % (optional) Specify satellite ID directly to set up data capture, for % example HelperSatelliteDataCapture(cfg,27424) obj = HelperSatelliteDataCapture(cfg,satelliteList(1,:), ... NotificationEmail=notificationEmail,S3Bucket=s3Bucket)
obj = HelperSatelliteDataCapture with properties: SatelliteID: 27424 SatelliteName: "AQUA" GroundStation: "Ohio 1" NotificationEmail: "" S3Bucket: "" Contact details ContactStatus: [] ContactStartTime: [] ContactDuration: [] Captured data files IQDataFile: [] ProcessedDataFile: []
If you opt for notifications, AWS sends confirmation email to the address you specify. Open the email and select Confirm subscription to receive more alerts. This figure shows a sample email received from the AWS notification service.
List Time of Satellite Contacts
List the start time and duration of a contact between satellite and the specified ground station by using the listContacts
function.
contactList = listContacts(obj)
....
contactList=13×2 table
StartTime Duration
___________________ ________
2024-06-28 23:27:43 00:08:50
2024-06-29 01:03:44 00:11:43
2024-06-29 13:03:06 00:10:59
2024-06-29 14:40:37 00:07:24
2024-06-30 00:07:18 00:10:55
2024-06-30 01:49:00 00:06:17
2024-06-30 12:09:57 00:04:31
2024-06-30 13:43:21 00:09:01
2024-06-30 15:22:22 00:03:49
2024-06-30 23:16:00 00:03:13
2024-07-01 00:52:16 00:06:33
2024-07-01 12:46:40 00:10:19
2024-07-01 14:23:38 00:08:07
Schedule Contact
Schedule a contact to configure the AWS notification service and capture satellite data into an S3 bucket. Schedule a contact with these input parameters by using the scheduleContact
function.
(optional)
StartTime
— Specify the start time of the contact(optional)
Duration
— Specify the duration (in minutes) for which to capture data
If you do not specify StartTime
or Duration
, AWS captures the data at the first available slot of the satellite.
scheduleContactNow = false; if scheduleContactNow == true startTime = contactList.StartTime(1); duration = 1; scheduleContact(obj,StartTime=startTime,Duration=duration); end
You can list the scheduled contacts by specifying the "scheduled"
argument to the listContacts
function.
scheduledContactList = listContacts(obj,"scheduled");
If you have opted for email notifications, the AWS notification service notifies you regarding the contact status.
Save and Load
Save the HelperSatelliteDataCapture
object using the save
function.
save("scheduledObj","obj")
To load scheduledObj
, double-click the MAT file or use the load
function. AWS updates the contact status automatically.
load("scheduledObj")
Cancel Contact
You can cancel your scheduled contact before the scheduled time. Before doing so, review the terms and conditions for canceling a scheduled contact, as well as the associated charges and pricing. To cancel a scheduled contact, uncomment and use the cancelContact
function.
%cancelContact(obj)
You can list the cancelled contacts by specifying "cancelled" argument to the listContacts
function.
cancelledContactList = listContacts(obj,"cancelled");
Get Captured Data from S3 Bucket
After the successful completion of a contact, download the captured data files from the S3 bucket to a local workstation for further processing. Alternatively, you can provide a path to save the data files by using the downloadDataFile
function. Additionally, the updateContactStatus
function updates the data filenames for the IQ data file and the processed data file.
contactStatus = obj.updateContactStatus; if strcmp(contactStatus,"completed") downloadDataFile(obj) end
Demodulate and Decode IQ Samples
This example supports demodulation and decoding of IQ samples for offset quadrature phase shift keying (OQPSK) modulation. To demodulate and decode the captured IQ samples, read the captured samples in PCAP format, and write them to a VITA49 formatted file.
% Read payload data from PCAP file if exist("obj","var") if size(obj.IQDataFile,1) > 1 iqFile = obj.IQDataFile(1); % Using first captured data file else iqFile = "IQData.pcap"; end else iqFile = "IQData.pcap"; end removePcapHeader(iqFile,"IQData.bin"); % IQData.bin file contains the IQ data in VITA49 format
Read the payload data containing the baseband IQ samples by using vita49Reader
. The satellite controller must convey the managed properties of the IQ samples, such as bandwidth, modulation, and coding scheme. Use these managed parameters to control the CCSDS TM receiver.
vita49ReaderObj = vita49Reader("IQData.bin"); % Read 200 packets from the file, which corresponds to 13 frames in the % IQdata.bin file contextPacket = read(vita49ReaderObj,PacketType="context"); numPackets = 199; [dataPackets,~] = read(vita49ReaderObj,NumPackets=numPackets); awsIQSamples = [dataPackets(:).IQSamples];
Visualize the spectrum of a complex baseband signal.
scope = spectrumAnalyzer;
scope.SampleRate = contextPacket(1).SampleRate;
scope.SpectrumUnits = "dBW";
scope(awsIQSamples(:))
The sample rate of the collected AQUA satellite signal is 17.22 Msps, which is not an integer multiple of the symbol rate of 7.5 Msps. To process these AWS IQ samples, convert the sampling rate to sample per symbol times the symbol rate. This conversion allows for a slight deviation of 0.01% from the output of the sample rate to speed up the process.
satelliteID = 27424; switch satelliteID case 27424 % AQUA managedParams = struct(... PCMFormat = "NRZ-M",... ChannelCoding = "RS",... RSMessageLength = 223,... RSInterleavingDepth = 4,... IsRSMessageShortened = false, ... RSShortenedMessageLength = 223, ... Modulation = "OQPSK", ... PulseShapingFilter = "root raised cosine",... RolloffFactor = 0.5, ... SymbolRate = 7.5*10^6, ... SamplesPerSymbol = 4, ... NumNRZMEncoders = 2, ... InvertCCPath2 = false); case {43013,37849} % SUOMI NPP, NOAA-20(JPSS) managedParams = struct(... PCMFormat = "NRZ-M",... ChannelCoding = "concatenated",... RSMessageLength = 223,... RSInterleavingDepth = 4,... IsRSMessageShortened = false, ... RSShortenedMessageLength = 223, ... Modulation = "QPSK", ... RolloffFactor = 0.5, ... PulseShapingFilter = "root raised cosine",... SymbolRate = 15*10^6, ... SamplesPerSymbol = 4,... NumNRZMEncoders = 1, ... InvertCCPath2 = false); end inputSampleRate = contextPacket(1).SampleRate; % Sampling rate of input signal symbolRate = managedParams.SymbolRate; % Symbol rate is 7.5Msps for AQUA samplePerSymbol = managedParams.SamplesPerSymbol; % Samples per symbol is 4 for AQUA bandwidth = contextPacket(1).Bandwidth; % Bandwidth of the input signal src = dsp.SampleRateConverter(... Bandwidth=bandwidth, ... InputSampleRate=inputSampleRate, ... OutputRateTolerance=0.01, ... OutputSampleRate=symbolRate*samplePerSymbol); rxIn = src(awsIQSamples(:));
Visualize the spectrum of the converted samples.
rxScope = spectrumAnalyzer;
rxScope.SampleRate = symbolRate*samplePerSymbol;
rxScope.SpectrumUnits = "dBW";
rxScope(rxIn)
Use the CCSDS practical receiver to process these baseband IQ samples. Set the receiver settings according to the managedParams
values. The satellite controller communicates these managedParams
to the AWS ground station. These parameters do not vary for a specific satellite.
ccsdsrx = HelperCCSDSTMReceiver(... PCMFormat=managedParams.PCMFormat,... ChannelCoding=managedParams.ChannelCoding,... RSMessageLength=managedParams.RSMessageLength,... RSInterleavingDepth=managedParams.RSInterleavingDepth,... IsRSMessageShortened=managedParams.IsRSMessageShortened, ... RSShortenedMessageLength=managedParams.RSShortenedMessageLength, ... Modulation=managedParams.Modulation, ... RolloffFactor=managedParams.RolloffFactor, ... PulseShapingFilter=managedParams.PulseShapingFilter,... SamplesPerSymbol=managedParams.SamplesPerSymbol,... NumNRZMEncoders=managedParams.NumNRZMEncoders,... InvertCCPath2=managedParams.InvertCCPath2,... SampleRate=symbolRate*samplePerSymbol); mwDecodedBits = ccsdsrx(rxIn); % mwDecodedBytes contains demodulated decoded data from the CCSDS receiver mwDecodedBytes = bit2int(mwDecodedBits,8);
Match the demodulated and decoded data from the CCSDS practical receiver with the AWS processed data file. The AWS processed data file contains the demodulated and decoded data from the AWS SDR, which is in pcap format. Read and write the processed data file to a VITA49 formatted file for comparison.
if exist("obj","var") if size(obj.ProcessedDataFile,1) > 1 processedFile = obj.ProcessedDataFile(1); % Using first captured data file else processedFile = "ProcessedData.pcap"; end else processedFile = "ProcessedData.pcap"; end % Remove the PCAP header from the captured file to get VITA49 file removePcapHeader(processedFile,"ProcessedData.bin"); % ProcessedData.bin file contains the processed data in VITA49 format
Compare the data using the HelperDataCompare
function.
misMatchedPackets = HelperDataCompare(mwDecodedBytes,"ProcessedData.bin");
The mwDecodedBytes frame of 1 corresponds to the AWS processed file frame of 7. All 13 packets matched.
An empty misMatchedPackets
variable indicates that all the demodulated and decoded packets from the CCSDS receiver matched with the processed data file from AWS. When misMatchedPackets
is not empty, the HelperDataCompare
function returns information about mismatched packets.
Other Satellites
You can collect data from any of these satellites by changing the satellite NORAD ID in the HelperSatelliteDataCapture
function.
NOAA 20 JPSS 1 (NORAD ID 43013) — This satellite was launched in 2017, and orbits at an altitude of 825 km. It carries five sensors for studying land and water.
SUOMI NPP (NORAD ID 37849) — This satellite was launched in 2011, and orbits at an altitude of 883 km. It carries four sensors that provide climate measurements.
TERRA (NORAD ID 25994) — This satellite was launched in 1999, and orbits at an altitude of 705 km. It carries five sensors for studying the surface of the Earth.
Appendix
The example uses these helper functions:
HelperAWSAccount.m
— Sign into AWS CLI through MATLAB by providing IAM usernameHelperSatellitelist.m
— List available satellites in your AWS accountHelperSatelliteDataCapture.m
— Set up satellite ID, ground station, S3 bucket and notification emailHelperCCSDSTMReceiver.m
— Demodulate and decode the IQ raw dataHelperDataCompare.m
— Compare the data demodulated and decoded by the CCSDS receiver against the data demodulated and decoded by AWS.
Local Functions
function removePcapHeader (pcapFile, newFile) % This function removes the PCAP header from a file captured on AWS and % saves the remaining data to a new binary file. fileID = fopen(pcapFile, 'r'); % Open the PCAP file for reading fread(fileID, 24, 'uint8'); % Skip the global header (24 bytes) outputFileID = fopen(newFile,"w"); % Open the output binary file % Follow these steps to remove the PCAP header: % 1. Ignore the first 16 bytes of the PCAP packet header. % 2. Ignore the subsequent 14 bytes of the Ethernet header. % 3. Ignore the next 20 bytes of the Internet Protocol version 4 (IPV4) header. % 4. Ignore the subsequent 8 bytes of the User Datagram Protocol (UDP) header. packetHeaderLength = 16; % PCAP header length while ~feof(fileID) packetHeader = fread(fileID, packetHeaderLength, '*uint8'); if length(packetHeader) < packetHeaderLength break; end capturedLength = typecast(packetHeader(9:12),'uint32'); % Packet data contains IPV4 (20 bytes), UDP (8 bytes) and Ethernet (14 % bytes) and the rest is VITA49 data packetData = fread(fileID, capturedLength, '*uint8'); removeBytes = 42; if capturedLength > removeBytes fwrite(outputFileID, packetData(removeBytes + 1:end), '*uint8'); end end fclose(fileID); fclose(outputFileID); end