Main Content

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.

  1. 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.

  2. 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.

onboarding.png

Configure AWS CLI

In this example, you configure the AWS CLI for accessing the AWS services by following these steps.

  1. Download and install the latest AWS CLI from the AWS Command Line Interface site.

  2. 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.

subscription.png

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=12×2 table
         StartTime         Duration
    ___________________    ________

    2023-06-27 12:54:20    00:10:56
    2023-06-27 14:31:56    00:06:39
    2023-06-27 23:58:35    00:11:18
    2023-06-28 12:01:16    00:06:05
    2023-06-28 13:35:39    00:08:49
    2023-06-28 23:03:53    00:08:39
    2023-06-29 00:39:49    00:11:49
    2023-06-29 12:39:51    00:10:59
    2023-06-29 14:17:25    00:07:14
    2023-06-29 23:44:38    00:10:56
    2023-06-30 01:26:10    00:06:36
    2023-06-30 11:47:55    00:04:29

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.

startTime = contactList.StartTime(1);
duration = 1;
scheduleContact(obj,StartTime=startTime,Duration=duration)
1   | satelliteDataCapture-27-06-2023-12-18-56 | CREATE_IN_PROGRESS   | 2023-06-27T06:50:21.936000+00:00  |
1   | MWSatelliteDataCapture                   | CREATE_IN_PROGRESS   | 2023-06-27T06:50:40.593000+00:00  |
2   | RenameFilesLambda                        | CREATE_IN_PROGRESS   | 2023-06-27T06:50:40.559000+00:00  |
3   | RenameFilesLambda                        | CREATE_IN_PROGRESS   | 2023-06-27T06:50:39.178000+00:00  |
4   | MWSatelliteDataCapture                   | CREATE_IN_PROGRESS   | 2023-06-27T06:50:39.034000+00:00  |
5   | MWSatelliteDataCaptureRole               | CREATE_COMPLETE      | 2023-06-27T06:50:37.583000+00:00  |
6   | MWS3BucketRole                           | CREATE_COMPLETE      | 2023-06-27T06:50:36.830000+00:00  |
7   | AquaDownlinkDemodDecodeAntennaConfig     | CREATE_COMPLETE      | 2023-06-27T06:50:26.749000+00:00  |
8   | TrackingConfig                           | CREATE_COMPLETE      | 2023-06-27T06:50:26.538000+00:00  |
9   | AquaDownlinkDemodDecodeAntennaConfig     | CREATE_IN_PROGRESS   | 2023-06-27T06:50:26.518000+00:00  |
10  | AquaDownlinkDigIfAntennaConfig           | CREATE_COMPLETE      | 2023-06-27T06:50:26.472000+00:00  |
11  | TrackingConfig                           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:26.313000+00:00  |
12  | AquaDownlinkDigIfAntennaConfig           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:26.242000+00:00  |
13  | MWS3BucketName                           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.946000+00:00  |
14  | MWSatelliteDataCaptureRole               | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.573000+00:00  |
15  | MWS3BucketRole                           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.402000+00:00  |
16  | AquaDownlinkDemodDecodeAntennaConfig     | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.384000+00:00  |
17  | TrackingConfig                           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.266000+00:00  |
18  | MWSatelliteDataCaptureRole               | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.238000+00:00  |
19  | AquaDownlinkDigIfAntennaConfig           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.108000+00:00  |
20  | MWS3BucketName                           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.091000+00:00  |
21  | MWS3BucketRole                           | CREATE_IN_PROGRESS   | 2023-06-27T06:50:25.073000+00:00  |
1   | AquaMissionProfile                       | CREATE_IN_PROGRESS   | 2023-06-27T06:51:04.215000+00:00  |
2   | S3RecordingDownlinkConfig                | CREATE_COMPLETE      | 2023-06-27T06:51:02.820000+00:00  |
3   | S3RecordingDemodDecodeConfig             | CREATE_COMPLETE      | 2023-06-27T06:51:02.739000+00:00  |
4   | S3RecordingDownlinkConfig                | CREATE_IN_PROGRESS   | 2023-06-27T06:51:02.513000+00:00  |
5   | S3RecordingDemodDecodeConfig             | CREATE_IN_PROGRESS   | 2023-06-27T06:51:02.377000+00:00  |
6   | S3RecordingDownlinkConfig                | CREATE_IN_PROGRESS   | 2023-06-27T06:51:01.166000+00:00  |
7   | S3RecordingDemodDecodeConfig             | CREATE_IN_PROGRESS   | 2023-06-27T06:51:00.969000+00:00  |
8   | MWS3BucketPolicy                         | CREATE_COMPLETE      | 2023-06-27T06:50:59.781000+00:00  |
9   | MWS3BucketPolicy                         | CREATE_IN_PROGRESS   | 2023-06-27T06:50:48.663000+00:00  |
10  | MWSatelliteDaCaptureRule                 | CREATE_IN_PROGRESS   | 2023-06-27T06:50:48.110000+00:00  |
11  | MWS3BucketPolicy                         | CREATE_IN_PROGRESS   | 2023-06-27T06:50:47.994000+00:00  |
12  | RenameCapturedFilesRule                  | CREATE_IN_PROGRESS   | 2023-06-27T06:50:47.831000+00:00  |
13  | MWSatelliteDaCaptureRule                 | CREATE_IN_PROGRESS   | 2023-06-27T06:50:47.748000+00:00  |
14  | RenameCapturedFilesRule                  | CREATE_IN_PROGRESS   | 2023-06-27T06:50:47.572000+00:00  |
15  | MWS3BucketName                           | CREATE_COMPLETE      | 2023-06-27T06:50:46.642000+00:00  |
16  | MWSatelliteDataCapture                   | CREATE_COMPLETE      | 2023-06-27T06:50:46.385000+00:00  |
17  | RenameFilesLambda                        | CREATE_COMPLETE      | 2023-06-27T06:50:46.328000+00:00  |
1   | AquaMissionProfile                       | CREATE_COMPLETE      | 2023-06-27T06:51:05.663000+00:00  |
2   | AquaMissionProfile                       | CREATE_IN_PROGRESS   | 2023-06-27T06:51:05.296000+00:00  |
1   | PermissionToInvokeLambda                 | CREATE_IN_PROGRESS   | 2023-06-27T06:51:50.252000+00:00  |
2   | PermissionForRenameFilesLambda           | CREATE_IN_PROGRESS   | 2023-06-27T06:51:49.989000+00:00  |
3   | PermissionToInvokeLambda                 | CREATE_IN_PROGRESS   | 2023-06-27T06:51:49.921000+00:00  |
4   | PermissionForRenameFilesLambda           | CREATE_IN_PROGRESS   | 2023-06-27T06:51:49.571000+00:00  |
5   | MWSatelliteDaCaptureRule                 | CREATE_COMPLETE      | 2023-06-27T06:51:48.601000+00:00  |
6   | RenameCapturedFilesRule                  | CREATE_COMPLETE      | 2023-06-27T06:51:48.223000+00:00  |
1   | satelliteDataCapture-27-06-2023-12-18-56 | CREATE_COMPLETE      | 2023-06-27T06:52:01.871000+00:00  |

The ground station contact has been scheduled successfully at 2023-06-27 12:54:20.

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.

schedule.png

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
pcapObj = pcapReader(iqFile);
pcapData = pcapObj.readAll;
% Write the PCAP formatted data file to VITA49 formatted data file
data = arrayfun(@(x) pcapData(x).Packet.eth.Payload,1:numel(pcapData), ...
    UniformOutput=false);
% To create a VITA 49 formatted data file from the captured satellite data,
% ignore the first 20 bytes of the Internet Protocol version 4 (IPv4)
% header and the following 8 bytes of the User Datagram Protocol (UDP)
% header from the payload of pcapReader.
fileID = fopen("IQData.bin","w");
cellfun(@(x) fwrite(fileID,x(29:end)),data);
fclose(fileID);

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
pcapObj = pcapReader(processedFile);
pcapData = pcapObj.readAll;
data = arrayfun(@(x) pcapData(x).Packet.eth.Payload,1:numel(pcapData), ...
    UniformOutput=false);
% Remove 20 bytes of IPV4 header and 8 bytes of UDP header
fileID = fopen("ProcessedData.bin","w");
cellfun(@(x) fwrite(fileID,x(29:end)),data);
fclose(fileID);

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:

See Also

Objects

Related Topics