#!/bin/bash
#
# Date: 02/2023
# Author: Industrial Shields®
# Hardware: Raspberry PLC and SARA R412M
# Description:
# This script prompts the user for the ISP's APN, username, password, and the desired RAT as parameters.
# It then configures the modem by passing AT commands and connects to the network using CHAP authentication.
# Finally, it creates a service named pppd so that the desired RAT (LTE Cat M1, NB-IoT, or GPRS/eGPRS)
# is automatically configured with a network interface called ppp0 every time the Raspberry PLC is started.

set -euo pipefail

LTE="LTE Cat M1 or Long Term Evolution (4G) category M1"
NB="Narrow Band IoT (NB-IoT)"
GPRS="GPRS/eGPRS"

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
echo -e "\r\nPlease select the desired Radio Access Technology (1, 2 or 3):"
echo "--> 1: ${LTE}"
echo "--> 2: ${NB}"
echo "--> 3: ${GPRS}"
read VALUE

if [[ $VALUE =~ ^[1-3]$ ]]; then
    RAT=$VALUE
else
    echo "Invalid value. Please enter a value between 1 and 3."
    exit 1
fi

case ${RAT} in
	1)
		RAT="lte-m"
		echo "OK" ${LTE}
		;;
	2)
		RAT="nb-iot"
		echo "OK" ${NB}
		;;
	3)
		RAT="gprs"
		echo "OK" ${GPRS}
		;;
	*)
		echo "Value is not 1, 2 or 3"
		exit 1
		;;
esac

SERVICE="pppd"
#DNS1="10.4.0.240"
#DNS2="10.4.0.230"

######################################################################
cat > /lib/systemd/system/${SERVICE}.service <<EOF
[Unit]
Description=SARA-R412M setup

[Service]
Type=simple
ExecStart=/usr/bin/python /usr/src/network-setup.py ${RAT}
ExecStop=/usr/bin/python /usr/src/network-setup.py clear
Restart=on-failure
RemainAfterExit=true
KillSignal=SIGKILL

[Install]
WantedBy=multi-user.target
EOF

######################################################################
#if ! grep -q '^nameserver ${DNS1}$' /etc/resolv.conf; then
#	echo "nameserver ${DNS1}" >> /etc/resolv.conf
#fi
#if ! grep -q '^nameserver ${DNS2}$' /etc/resolv.conf; then
#	echo "nameserver ${DNS2}" >> /etc/resolv.conf
#fi

#touch /etc/ppp/resolv.conf

#if ! grep -q '^nameserver ${DNS1}$' /etc/ppp/resolv.conf; then
#        echo "nameserver ${DNS1}" >> /etc/ppp/resolv.conf
#fi
#if ! grep -q '^nameserver ${DNS2}$' /etc/ppp/resolv.conf; then
#        echo "nameserver ${DNS2}" >> /etc/ppp/resolv.conf
#fi

######################################################################
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/src/network-setup.py <<EOF
import subprocess
import time
import serial
import os
import sys
import threading
import datetime

filename = os.path.basename(__file__)
usage = f"Usage: 'python {filename} gprs / nb-iot / lte-m / clear'"

# Manage arguments
if len(sys.argv) != 2:
	print(usage)
	sys.exit(1)
else:
	# Get argument
	arg = sys.argv[1]

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

# Serial port settings
serial_port = '/dev/ttySC0'
baud_rate = 115200

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

# AT commands for NB-IoT
nb_iot_cmds = [
	'+++',
	'Z',
	'',
	'+CFUN=0',
	'+CPSMS=0',
	'+UPSMR=0',
	'+UMNOPROF=100',
	'+CPWROFF',
	'+URAT=8',
	'+CPWROFF',
	'+UBANDMASK=1,524420',
	'+CGDCONT=1,"IP","{}","0.0.0.0",0,0'.format(ISP['apn']),
	'+UAUTHREQ=1,2,"{}","{}"'.format(ISP['username'], ISP['password']),
	'+CFUN=1',
	'+CFUN=15',
#	'+UMNOPROF?',
#	'+URAT?',
#	'+UBANDMASK?',
#	'+CGDCONT?',
#	'+COPS?',
	'+CEREG?',
	'+CSQ',
	'D{}'.format(ISP['phone_number']),
]

# AT commands for LTE-Cat-M1 (also LTE-M)
lte_m_cmds = [
	'+++',
	'Z',
	'',
	'+CFUN=0',
	'+CPSMS=0',
	'+UPSMR=0',
	'+UMNOPROF=100',
	'+CPWROFF',
	'+URAT=7',
	'+CPWROFF',
	'+UBANDMASK=0,524420',
	'+CGDCONT=1,"IP","{}","0.0.0.0",0,0'.format(ISP['apn']),
	'+UAUTHREQ=1,2,"{}","{}"'.format(ISP['username'], ISP['password']),
	'+CFUN=1',
	'+CFUN=15',
#	'+UMNOPROF?',
#	'+URAT?',
#	'+UBANDMASK?',
	'+CEREG?',
	'+CSQ',
	'D{}'.format(ISP['phone_number']),
]

# PPP commands for setting the ppp0 interface
pppd_cmd = "sudo pppd debug updetach maxfail 0 dump noauth " 	"user {} password {} {} {} modem 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)

        if command in ['+CFUN=15','+CPWROFF']:
            print(f'Waiting 5 seconds...')
            time.sleep(5)
            print('OK')
            send_at_until_ok(ser)

        if command in ['+CGREG?','+CEREG?','+CSQ']:
            registered = wait_for_registration(ser, command)
            if not registered:
                break

    if registered:
        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, command):
    if command == '+CGREG?':
        expected_response = '+CGREG: 0,5'
    elif command == '+CEREG?':
        expected_response = '+CEREG: 0,5'
    elif command == '+CSQ':
        expected_response = "+CSQ: 14,99"

    timeout = 360
    start_time = time.time()
    while (time.time() - start_time) < timeout:
        rx = send_at(ser, command)
        if expected_response in rx:
            return True
        elif command != '+CGREG?' and command != '+CEREG?' and '+CSQ:' in rx:
            csq_value = get_digit(rx)
            if csq_value >= 8.99:
                return True
        time.sleep(0.2)
    return False


######################################################################
def open_port():
    print(f'Opening serial port {serial_port} ...')
    start = time.time()
    port = serial.Serial(serial_port, baud_rate, timeout=1)
    end = time.time() - start
    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 rx.endswith(b'150000000\r\n'):
                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("OK")
            break


######################################################################
def send_at(ser, cmd, tries=7, timeout=1):
    write_cmd(ser, cmd)
    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'])

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

    finally:
        print("OK clear pppd")


######################################################################
def trigger_reset():
    print('Resetting...')
    subprocess.call(['/home/pi/test/analog/set-digital-output', 'EXP1_RST', '1'])
    time.sleep(11)
    subprocess.call(['/home/pi/test/analog/set-digital-output', 'EXP1_RST', '0'])
    time.sleep(2)
    print("OK")


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


######################################################################
def main():
    global arg
    if arg == 'clear':
        clear_pppd()
        sys.exit(0)

    timeSpent = time.time()
    now = datetime.datetime.now()
    print(now)
    try:
        ser = None
        clear_pppd()
        trigger_reset()

        ser = open_port()
        if ser.isOpen():
            print(f'Serial port {serial_port} opened')
            if arg == 'gprs':
                configure_modem(gprs_cmds, ser)
            elif arg == 'nb-iot':
                configure_modem(nb_iot_cmds, ser)
            elif arg == 'lte-m':
                configure_modem(lte_m_cmds, ser)
            elif arg == 'clear':
                clear_pppd()
            else:
                print(usage)

        else:
            print(f'Serial port {serial_port} could not open')

    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

######################################################################
chmod 755 /usr/src/network-setup.py
systemctl enable ${SERVICE}
if systemctl is-active ${SERVICE} >/dev/null 2>&1; then
	systemctl restart ${SERVICE}
else
	systemctl start ${SERVICE}
fi
systemctl daemon-reload

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