.. _iqstreaming:

======================
AIS Channels Streaming
======================

It is possible to stream the 4 filtered baseband AIS channels over the Ethernet interface, while the Polaris AIS receiver do the normal demodulation. 

Data Format
===========

Each stream is 16 bit complex signed integer with a sample rate of 96 Ksps. The data from the 4 AIS channels is interleaved and placed in a UDP packet with as size of 1448 and sent to port 53032. The first 8 bytes of each UDP packet is a sequence counter sent in little endian, this can be used to detect packet loss between the Polaris AIS receiver and the destination. The UDP packet format is show in :ref:`udp-packet-data-format`.

.. _udp-packet-data-format:

.. list-table:: UDP Packet Data Format

   * - Byte
     - 0-7
     - 8-11
     - 12-15
     - 16-19
     - 20-23
     - ...
     - 1444-1447
   * - Field content
     - Sequence cnt.
     - I0,Q0
     - I1,Q1
     - I2,Q2
     - I3,Q3
     - ...
     - I3,Q3


Example Setup
=============

In the following example the Polaris AIS is setup to stream samples to a PC with a fixed IP address set to 192.168.100.22 and on the same network as the Polaris AIS. The Polaris AIS will be configured to have IP address 192.168.100.20.

.. figure:: img/iq-stream-network.svg
   :align: center

   Example network setup

In the debug shell on the Polaris AIS:

- Follow :ref:`starting` to setup communication with the receiver.
- Connect a cable to the Ethernet connector
- prop set sys.net.ip 192.168.100.20
- prop set sys.net.prefix 24
- prop set sys.net.gw 192.168.100.1
- prop set sys.net.enable
- prop set demod.iq.dest.ip 192.168.100.22
- prop set demod.iq.enable 1

Now the Polaris AIS will start sending UDP packets to 192.168.100.22.
The code example below, written in Python, show how to receive the data from a PC.

.. code-block:: python

   #!/usr/bin/env python
   
   import struct
   import socket
   
   HOST = ''                 # Symbolic name meaning all available interfaces
   PORT = 53032              # Port that the Polaris AIS is sending data to
   
   POLARIS_AIS_UDP_SIZE = 1448
   
   with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
       s.bind((HOST, PORT))
       while True:
           data = s.recv(POLARIS_AIS_UDP_SIZE)
           seq_nr = struct.unpack("<Q", data[:8]) # Unpack sequence number
           iq_samples = struct.unpack("<" + "h" * 720, data[8:]) # Unpack signed shorts
           i0 = iq_samples[0::8] # Starting from 0 every 8 sample is from channel I0
           q0 = iq_samples[1::8] # Starting from 1 every 8 sample is from channel Q0
           i1 = iq_samples[2::8] # Starting from 2 every 8 sample is from channel I1
           q1 = iq_samples[3::8] # Starting from 3 every 8 sample is from channel Q1
           i2 = iq_samples[4::8] # Starting from 4 every 8 sample is from channel I2
           q2 = iq_samples[5::8] # Starting from 5 every 8 sample is from channel Q2
           i3 = iq_samples[6::8] # Starting from 6 every 8 sample is from channel I3
           q3 = iq_samples[7::8] # Starting from 7 every 8 sample is from channel Q3
           print(f"Seq. nr. {seq_nr[0]}, {i0[0]:+06d}, {q0[0]:+06d}, {i1[0]:+06d}, {q1[0]:+06d}, {i2[0]:+06d}, {q2[0]:+06d}, {i3[0]:+06d}, {q3[0]:+06d}")

For each UDP packet the script extracts the sequence number and I/Q from the 4 sample streams. The channel mapping are as follows:

- CH0: 156.825 MHz
- CH1: 156.775 MHz
- CH2: 161.975 MHz
- CH3: 162.025 MHz
