#!/bin/bash
#
# Date: 02/2023
# Author: Industrial Shields®
# Hardware: Raspberry PLC and LARA-L6004D
#
######################################################################
set -xeuo pipefail

echo "Please enter your APN"
read APN
echo -e "\r\nPlease enter your ISP username"
read USERNAME
echo -e "\r\nPlease enter your ISP password"
read PASSWORD

SERVICE1="pppd"
SERVICE2="check-ppp0"

######################################################################
cat > /lib/systemd/system/${SERVICE1}.service <<EOF
[Unit]
Description=LARA-L6004D setup

[Service]
Type=simple
ExecStart=/usr/bin/python /usr/local/bin/network-setup.py
ExecStop=/usr/bin/sudo /usr/bin/poff -a
Restart=on-failure
RemainAfterExit=true

[Install]
WantedBy=multi-user.target
EOF

######################################################################
if ! grep -q '^DefaultTimeoutStopSec=5s$' /etc/systemd/system.conf; then
    echo "DefaultTimeoutStopSec=5s" >> /etc/systemd/system.conf
fi

######################################################################
cat > /etc/ppp/options <<EOF
EOF

######################################################################
cat > /etc/ppp/chap-secrets <<EOF
'${USERNAME}' * '${PASSWORD}' *
EOF

######################################################################
cat > /usr/local/bin/network-setup.py <<EOF
import subprocess
import time
import serial
import os
import sys
import threading
import datetime

# ISP settings
ISP = {
    'apn': '${APN}',
    'username': '${USERNAME}',
    'password': '${PASSWORD}',
    'phone_number': '*99***1#',
}

# Serial port settings
serial_port = '/dev/ttySC1'

# AT commands to set GPRS configuration
gprs_cmds = [
    '+++',
    'Z',
    '+CFUN=0',
    '+CPSMS=0',
    '+UPSMR=0',
    '+UMNOPROF?',
    '+URAT?',
    '+UBANDMASK?',
    '+CGDCONT=1,"IP","{}","0.0.0.0",0,0'.format(ISP['apn']),
    '+UAUTHREQ=1,2,"{}","{}"'.format(ISP['password'], ISP['username']),
    '+CFUN=1',
    '+CEREG?',
    '+CSQ',
    'D{}'.format(ISP['phone_number']),
]

# Baud rates to try
baud_rates = [115200, 57600, 38400, 9600, 14400, 19200, 28800]

baud_rate = 115200

# PPP commands for setting the ppp0 interface
pppd_cmd = "sudo pppd debug updetach maxfail 0 dump noauth user {} password {} {} {} local asyncmap 0 default-mru mtu 1500 lcp-echo-failure 3 lcp-echo-interval 10 ipcp-accept-local ipcp-accept-remote noipdefault ipcp-restart 10 ipcp-max-configure 30 ipcp-max-failure 10 defaultroute replacedefaultroute noipv6 usepeerdns ".format(ISP['username'], ISP['password'], serial_port, baud_rate)
PPP_COMMANDS = [pppd_cmd]

######################################################################
def configure_modem(commands, ser):
    cmd_fails = 0
    if ser is None:
        print(f'Serial port {serial_port} could not open')
        sys.exit(1)

    for command in commands:
        time.sleep(0.2)
        rx = send_at(ser, command)
        if rx is None:
            cmd_fails += 1
            manage_fails(ser, cmd_fails)

        elif command == '+UMNOPROF?':
            if '+UMNOPROF: 90' not in rx:
                send_at(ser, '+CFUN=0')
                send_at(ser, '+UMNOPROF=90')
                send_at(ser, '+CFUN=16')

        elif command == '+URAT?':
            if '+URAT: 3,2,0' not in rx:
                send_at(ser, '+CFUN=0')
                send_at(ser, '+URAT=3,2,0')
                send_at(ser, '+CFUN=16')

        elif command == '+UBANDMASK?':
            if '+UBANDMASK: 3,2061752998111,0,2,562950035734912' not in rx:
                send_at(ser, '+UBANDMASK=3,2061752998111,0,2,562950035734912')

        elif command == '+CEREG?':
            registered = wait_for_registration(ser)
            if not registered:
                break

        elif command == '+CSQ':
            min_value = 5.99
            timeout = 360
            start_time = time.time()
            while (time.time() - start_time) < timeout:
                rx = send_at(ser, command)
                csq_value = get_digit(rx)
                if csq_value >= min_value and csq_value != 99.99:
                    rssi = True
                    break

    if registered and rssi:
        time.sleep(1)
        for command in PPP_COMMANDS:
            subprocess.run(command.split())


######################################################################
def get_digit(rx):
    csq_value = rx.split(":")[1].strip().replace(",", ".")
    csq_num = ''
    for char in csq_value:
        if char == '.' or char.isdigit():
            csq_num += char
        elif csq_num and not char.isdigit():
            break

    if csq_num:
        csq_float = float(csq_num)
        return csq_float

######################################################################
def wait_for_registration(ser):
    expected_responses = ['+CGREG: 0,1', '+CGREG: 0,5', '+CEREG: 0,1', '+CEREG: 0,5']
    commands = ['+CGREG?', '+CEREG?']

    timeout = 360
    start_time = time.time()
    while (time.time() - start_time) < timeout:
        for command in commands:
            rx = send_at(ser, command)
            if rx is not None:
                for response in expected_responses:
                    if response in rx:
                        return True
        time.sleep(0.2)

    return False

######################################################################
def open_port(baud_rate):
    print(f'opening serial port {serial_port} with baud rate {baud_rate} ...')
    port = serial.Serial(serial_port, baud_rate, timeout=1)
    print('ok')
    port.flushInput()
    time.sleep(1)
    print('ok')
    return port

######################################################################
def write_cmd(ser, cmd):
    if cmd != '+++':
        cmd = f'AT{cmd}\r\n'

    print(f">> Sending {cmd}")
    ser.write(cmd.encode('utf-8'))

######################################################################
def read_response(ser, timeout=0.2):
    start_time = time.time()
    rx = b''
    count = 0
    while (time.time() - start_time) < timeout:
        rx_bytes = ser.inWaiting()
        if rx_bytes:
            rx += ser.read(rx_bytes)
            if rx.endswith(b'OK\r\n') or b'CONNECT' in rx:
                return rx.decode('utf-8', errors='ignore')
    return None

######################################################################
def manage_fails(ser, cmd_fails):
    print(f"{cmd_fails} commands failed")
    if cmd_fails >= 4:
        close_port(ser)
        main()
        sys.exit(0)

######################################################################
def send_at_until_ok(ser):
    while True:
        write_cmd(ser, '')
        time.sleep(0.1)
        response = read_response(ser, 1)
        if response is not None and 'OK' in response:
            ser.flush()
            print("Ready")
            break

######################################################################
def send_at(ser, cmd, tries=5, timeout=1):
    write_cmd(ser, cmd)
    if cmd == '+CFUN=16':
        time.sleep(5)
    for i in range(tries):
        response = read_response(ser, timeout)
        print(f'{response}')
        if response is not None:
            return response
    print(f"No response for {cmd} command")
    return None

######################################################################
def clear_pppd():
    try:
        output = subprocess.check_output(['pgrep', 'pppd'], stderr=subprocess.STDOUT)
        if output:
            subprocess.call(['sudo', 'killall', 'pppd'])
            subprocess.call(['sudo', 'poff', '-a'])

        output = subprocess.check_output(['ifconfig', 'ppp0'], stderr=subprocess.STDOUT)
        if b'ppp0:' in output:
            subprocess.call(['sudo', 'ifconfig', 'ppp0', 'down'])
            subprocess.call(['sudo', 'killall', 'pppd'])
            subprocess.call(['sudo', 'poff', '-a'])

        time.sleep(5)

    except subprocess.CalledProcessError as e:
        output = e.output
        pass

######################################################################
def close_port(ser):
    if ser.isOpen():
        print("closing port...")
        ser.flushInput()
        ser.flushOutput()
        ser.close()

######################################################################
def main():
    timeSpent = time.time()
    now = datetime.datetime.now()
    print(now)
    try:
        ser = None
        clear_pppd()

        for baud_rate in baud_rates:
            ser = open_port(baud_rate)
            if ser.isOpen():
                print(f'Serial port {serial_port} opened with baud rate {baud_rate}')
                response = send_at(ser, '', tries=3)
                if response and 'OK' in response:
                    rx = send_at(ser, '+IPR=115200', tries=1)
                    break
                else:
                    ser.close()

        if baud_rate:
            ser = open_port(115200)
            if ser.isOpen():
                print(f'Serial port {serial_port} opened with baud rate 115200')
                configure_modem(gprs_cmds, ser)
            else:
                print('Failed to open serial port with baud rate 115200')
        else:
            print(f'Unable to open serial port {serial_port} with any baud rate')

    except Exception as e:
        print("Exception: ", e)

    finally:
        close_port(ser)
        timeSpent = time.time() - timeSpent
        print("-------- TIME SPENT ", timeSpent)


######################################################################
# Start
if __name__ == "__main__":
    main()

EOF

#################################################################################################
cat > /usr/local/bin/$SERVICE2.py <<EOF
import subprocess
import time

def check_ppp0_status():
    try:
        output = subprocess.check_output("cat /sys/class/net/ppp0/carrier", shell=True).decode()
        return str(1) in output
    except subprocess.CalledProcessError:
        return False

while True:
    ppp0_status = check_ppp0_status()

    if not ppp0_status:
        print("The ppp0 interface is not active. Starting ppp0...")
        try:
            subprocess.check_call("sudo systemctl restart ${SERVICE1}", shell=True)
            print("Waiting for ppp0 to finish initializing...")
            time.sleep(60)
        except subprocess.CalledProcessError:
            print("Error starting ppp0.")
    else:
        continue

    time.sleep(5)
EOF

#################################################################################################
cat > /lib/systemd/system/${SERVICE2}.service <<EOF
[Unit]
Description=Check ppp0 status

[Service]
Type=simple
ExecStart=/usr/bin/sudo /usr/bin/python /usr/local/bin/${SERVICE2}.py

[Install]
WantedBy=multi-user.target
EOF

######################################################################
chmod 755 /usr/local/bin/network-setup.py
chmod 755 /usr/local/bin/${SERVICE2}.py

systemctl daemon-reload

SERVICES=("${SERVICE1}" "${SERVICE2}")
for service in "${SERVICES[@]}"; do
    systemctl enable "$service"
    systemctl restart "$service"
done

######################################################################
sync
exit 0
