#!/usr/bin/env python3
"""
sample_streaming.py - Stream continuous I/Q samples from a GRX receiver.

This example uses the Sample Streaming API to receive a continuous
stream of raw I/Q samples from a specific radio channel. This is
useful for generic frequency monitoring, recording, or feeding data
into custom signal processing pipelines - similar to using an SDR.

Usage:
    python sample_streaming.py [HOST]

Arguments:
    HOST    IP address or hostname of the GRX (default: YOUR_GRX_IP)

Prerequisites:
    pip install grpcio grpcio-tools
    Run gen_pb.sh to generate the Python bindings.

Note:
    Continuous I/Q streaming produces large amounts of data. At 12 MHz
    sample rate with 16-bit I/Q, the data rate is ~48 MB/s. Make sure
    your network link and processing pipeline can handle the throughput.
    Use the requested_blocks parameter to limit the stream duration.
"""

import struct
import sys

import grpc

# Import the generated gRPC stubs for the Sample Streaming API and Common types
import Common_pb2 as common_pb2
import Samplestreamingd_pb2 as samplestreamingd_pb2
import Samplestreamingd_pb2_grpc

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------

host = sys.argv[1] if len(sys.argv) >= 2 else "YOUR_GRX_IP"

# The Sample Streaming API listens on TCP port 5308
SAMPLESTREAMINGD_PORT = 5308

# Identify the radio channel to stream from.
# For a GRX1090, use Band=BAND_1090_MHZ, per_band_index=0.
# Adjust for your specific receiver configuration.
RADIO_ID = common_pb2.RadioIdentification(
    band=common_pb2.BAND_1090_MHZ,
    per_band_index=0,
)

# Number of sample blocks to receive (0 = indefinite)
NUM_BLOCKS = 10

# ---------------------------------------------------------------------------
# Connect and query stream properties
# ---------------------------------------------------------------------------

print(f"Connecting to {host}:{SAMPLESTREAMINGD_PORT}...")
channel = grpc.insecure_channel(f"{host}:{SAMPLESTREAMINGD_PORT}")
samplestreamingd = Samplestreamingd_pb2_grpc.SamplestreamingdStub(channel)

# Query the properties of this radio channel's sample stream
props = samplestreamingd.GetStreamProperties(
    samplestreamingd_pb2.GetStreamPropertiesRequest(
        radio_identification=RADIO_ID
    )
)

print(f"\nStream Properties:")
print(f"  Center frequency:   {props.center_frequency / 1e6:.3f} MHz")
print(f"  Sample rate:        {props.sample_rate / 1e6:.3f} MSps")
print(f"  Calibration value:  {props.calibration_value:.2f} dB")
print(f"  (Use: level_dBm = {props.calibration_value:.2f} + 10*log10(I^2 + Q^2))")

# ---------------------------------------------------------------------------
# Start streaming I/Q sample blocks
# ---------------------------------------------------------------------------

request = samplestreamingd_pb2.StartStreamRequest(
    radio_identification=RADIO_ID,
    requested_blocks=NUM_BLOCKS,  # 0 for indefinite
)

stream = samplestreamingd.StartStream(request)

print(f"\nStreaming {NUM_BLOCKS} sample blocks...\n")

total_samples = 0
total_lost = 0

try:
    for block in stream:
        # Each block contains raw I/Q samples as bytes (signed 16-bit interleaved)
        raw = block.samples
        num_samples = len(raw) // 4  # 4 bytes per I/Q pair (2x int16)

        total_samples += num_samples
        total_lost = block.lost_blocks

        # Parse the first few samples for display
        num_preview = min(3, num_samples)
        if num_preview > 0:
            pairs = struct.unpack(f"<{num_preview * 2}h", raw[:num_preview * 4])
            preview = "  ".join(f"({pairs[2*i]:+6d}, {pairs[2*i+1]:+6d})"
                                for i in range(num_preview))
        else:
            preview = "(empty)"

        print(f"Block: timestamp={block.block_timestamp}  "
              f"samples={num_samples}  "
              f"lost_blocks={block.lost_blocks}  "
              f"preview={preview}")

except KeyboardInterrupt:
    print("\nStopped.")
except grpc.RpcError as e:
    if e.code() != grpc.StatusCode.CANCELLED:
        print(f"\ngRPC error: {e.details()}")

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------

print(f"\nTotal samples received: {total_samples}")
print(f"Lost blocks: {total_lost}")

channel.close()
