# Example:  demonstrates TCP/IP TXMODE via HTTP POST of telemetry data

"""
HTTP POST DEMO USING Python TCPIP TX MODE
=========================================

This script shows how to use an HTTP POST to send telemetry data
via HTTP using Python TCPIP as the telemetry mode.

The script will generate an HTTP POST header which includes some
custom information (in this case, battery volage and station name)

Below is an example of the HTTP header generated by the script, as
well as the content "This is a test message":

POST / HTTP/1.1
Host: httpbin.org
Content-Type: text/plain
Content-Length: 22
Battery-Voltage: 10.70
Station-Name: Sutron XLink

This is a test message


If you are using a version of LinkComm which will not let you set the TXMODE
to Python TCPIP (but are running a version of firmware that supports it) you
will need to click the "Test Script File..." button and the script will set the
necessary fields. If you need to change the setup after that, be sure to
click "Test Script File..." button again.

The public test server httpbin.org is used to perform the test transmission
and verify the result. The last result can be displayed by running the
script task display_results.

With the following TX1 setup, this script will transmit sensor data via secure HTTPS::

!TX1 Enable=On
!TX1 Radio Type=Cell
!TX1 Kind=Scheduled
!TX1 Label=HTTP POST
!TX1 Scheduled Interval=01:00:00
!TX1 Scheduled Time=00:00:00
!TX1 Data Source=Measurement
!TX1 Format=CSV
!TX1 Custom Script Format=Off
!TX1 Mode=Python TCPIP
!TX1 Secure=On
!TX1 Use Certificate=Off
!TX1 Mode Function=send_message_http
!TX1 Main Server=httpbin.org
!TX1 Backup Server=
!TX1 Server Port=443
!TX1 Server Username=
!TX1 Server Password=
!TX1 Server Path=post

"""

from sl3 import *
from os import stat, urandom

last_results = "No results so far"

def receive_data(socket, amount=1024, iterations=120):
    """
    Receive data from a socket.

    This function calls socket.recv(amount) up to 'iterations' times, 
    concatenating and returning the result. It exits early if after 
    receiving data on one iteration it doesn't receive data on the next.

    :param socket: The socket object to receive data from.
    :type socket: socket.socket
    :param amount: The maximum amount of data to be received at once, defaults to 1024.
    :type amount: int, optional
    :param iterations: The maximum number of iterations to attempt receiving data, defaults to 10.
    :type iterations: int, optional
    :return: The concatenated data received from the socket.
    :rtype: bytes
    """
    result = b''
    data_received = False

    for _ in range(iterations):
        data = socket.recv(amount)
        if data:
            result += data
            data_received = True
        elif data_received:
            break
        else:
            data_received = False

    return result



@TXMODE
def send_message_http(socket, message, file_name):
    """
    Function to send a telemetry message via HTTP POST to httpbin.org.

    :param socket: Open socket for sending/receiving data.
    :param message: the telemetry data we want to send, generated using standard formatting
    :param file_name: unused as we send the message
    :return: 0 if HTTP POST was accepted and acknowledged, 1 if there was an issue.
    """
    global last_results

    if message is None:
        last_results = "Expected message tranmission"
        print(last_results)
        return 0  # do not retry

    msg_len = len(message)
    if msg_len == 0:
        last_results = "Message length is  0 bytes"
        print(last_results)
        return 0  # do not retry

    CRLF = '\r\n'
    last_results = "Sending message {} bytes".format(msg_len)
    
    try:
        # Prepare the HTTP POST request headers
        content_length = msg_len

        path = setup_read("!TX{} Server Path".format(index()))
        host = setup_read("!TX{} Main Server".format(index()))

        headers = [
            'POST /{} HTTP/1.1'.format(path),
            'Host: httpbin.org',
            'Content-Type: text/plain',
            'Content-Length: {}'.format(content_length),
            ]

        # Custom header entries can go here (it's easiest to avoid spaces in the field names):
        custom_headers = [
            'Battery-Voltage: {:.2f}'.format(batt()),
            'Station-Name: {}'.format(setup_read("!Station Name")),
            ]

        # Concatenate all headers into a single byte string
        headers_data = CRLF.join(headers) + CRLF + CRLF.join(custom_headers) + 2*CRLF

        last_results = "Sending headers"

        # Send the header
        socket.send(headers_data)

        last_results = "Sending message"
        
        # Send the message in 1KB chunks
        chunk_size = 1024
        for i in range(0, len(message), chunk_size):
            chunk = message[i:i + chunk_size]
            socket.send(chunk)

        if is_being_tested():
            print("A server reply cannot be tested")
            return 0

        # Set a timeout for receiving the HTTP response
        # please note that we are already connected to the server
        # so this is the timeout for server to reply to the POST
        socket.settimeout(5)  # Timeout seconds
        
        last_results = "Receiving reply"

        # Receive the HTTP response, allowing up to 30 seconds
        response = receive_data(socket)
        
        if len(response) == 0:
            response = "<timeout expired>".encode('utf-8')

        last_results = "Server Response:\r\n" + response.decode('utf-8')
        print(last_results)
        
        # Check if the server acknowledged the message
        if b'HTTP/1.1 200 OK' in response or b'HTTP/1.0 200 OK' in response:
            return 0  # HTTP POST accepted and acknowledged
        else:
            return 1  # Server did not acknowledge properly

    except Exception as e:
        last_results = "Transmission failed due to " + str(e)
        print(last_results)
        return 0  # Error occurred, do not retry


@TASK
def display_results():
    """
    Display the last results received from the HTTP Server
    """
    global last_results
    print("Last results:")
    print(last_results)


# in case version of LinkComm is being used which can't set these fields, we'll set them for it
if setup_read("tx1 label") == "HTTP POST":
    if setup_read("tx1 mode") != "Python TCPIP":
        setup_write("tx1 mode", "Python TCPIP")
    if setup_read("tx1 mode function") != "send_message_http":
        setup_write("tx1 mode function", "send_message_http")
