#!/usr/bin/env python

"""
The WiPy firmware update script. Transmits the specified firmware file
over FTP, and then resets the WiPy and optionally verifies that software
was correctly updated.

Usage:

    ./update-wipy.py --file "path_to_mcuimg.bin" --verify

Or:

    python update-wipy.py --file "path_to_mcuimg.bin"
"""

import sys
import argparse
import time
import socket
from ftplib import FTP
from telnetlib import Telnet


def print_exception(e):
    print ('Exception: {}, on line {}'.format(e, sys.exc_info()[-1].tb_lineno))


def ftp_directory_exists(ftpobj, directory_name):
    filelist = []
    ftpobj.retrlines('LIST',filelist.append)
    for f in filelist:
        if f.split()[-1] == directory_name:
            return True
    return False


def transfer_file(args):
    with FTP(args.ip, timeout=20) as ftp:
        print ('FTP connection established')

        if '230' in ftp.login(args.user, args.password):
            print ('Login successful')

            if '250' in ftp.cwd('/flash'):
                if not ftp_directory_exists(ftp, 'sys'):
                    print ('/flash/sys directory does not exist')
                    if not '550' in ftp.mkd('sys'):
                        print ('/flash/sys directory created')
                    else:
                        print ('Error: cannot create /flash/sys directory')
                        return False
                if '250' in ftp.cwd('sys'):
                    print ("Entered '/flash/sys' directory")
                    with open(args.file, "rb") as fwfile:
                        print ('Firmware image found, initiating transfer...')
                        if '226' in ftp.storbinary("STOR " + 'mcuimg.bin', fwfile, 512):
                            print ('File transfer complete')
                            return True
                        else:
                            print ('Error: file transfer failed')
                else:
                    print ('Error: cannot enter /flash/sys directory')
            else:
                print ('Error: cannot enter /flash directory')
        else:
            print ('Error: ftp login failed')

    return False


def reset_board(args):
    success = False

    try:
        tn = Telnet(args.ip, timeout=5)
        print("Connected via Telnet, trying to login now")

        if b'Login as:' in tn.read_until(b"Login as:", timeout=5):
            tn.write(bytes(args.user, 'ascii') + b"\r\n")

            if b'Password:' in tn.read_until(b"Password:", timeout=5):
                # needed because of internal implementation details of the WiPy's telnet server
                time.sleep(0.2)
                tn.write(bytes(args.password, 'ascii') + b"\r\n")

                if b'Type "help()" for more information.' in tn.read_until(b'Type "help()" for more information.', timeout=5):
                    print("Telnet login succeeded")
                    tn.write(b'\r\x03\x03') # ctrl-C twice: interrupt any running program
                    time.sleep(1)
                    tn.write(b'\r\x02') # ctrl-B: enter friendly REPL
                    if b'Type "help()" for more information.' in tn.read_until(b'Type "help()" for more information.', timeout=5):
                        tn.write(b"import machine\r\n")
                        tn.write(b"machine.reset()\r\n")
                        time.sleep(2)
                        print("Reset performed")
                        success = True
                    else:
                        print("Error: cannot enter friendly REPL")
                else:
                    print("Error: telnet login failed")

    except Exception as e:
        print_exception(e)
    finally:
        try:
            tn.close()
        except Exception as e:
            pass
        return success


def verify_update(args):
    success = False
    firmware_tag = ''

    def find_tag (tag):
        if tag in firmware_tag:
            print("Verification passed")
            return True
        else:
            print("Error: verification failed, the git tag doesn't match")
            return False

    retries = 0
    while True:
        try:
            # Specify a longer time out value here because the board has just been
            # reset and the wireless connection might not be fully established yet
            tn = Telnet(args.ip, timeout=10)
            print("Connected via telnet again, lets check the git tag")
            break
        except socket.timeout:
            if retries < 5:
                print("Timeout while connecting via telnet, retrying...")
                retries += 1
            else:
                print('Error: Telnet connection timed out!')
                return False

    try:
        firmware_tag = tn.read_until (b'with CC3200')
        tag_file_path = args.file.rstrip('mcuimg.bin') + 'genhdr/mpversion.h'
        
        if args.tag is not None:
            success = find_tag(bytes(args.tag, 'ascii'))
        else:
            with open(tag_file_path) as tag_file:
                for line in tag_file:
                    bline = bytes(line, 'ascii')
                    if b'MICROPY_GIT_HASH' in bline:
                        bline = bline.lstrip(b'#define MICROPY_GIT_HASH ').replace(b'"', b'').replace(b'\r', b'').replace(b'\n', b'')
                        success = find_tag(bline)
                        break

    except Exception as e:
        print_exception(e)
    finally:
        try:
            tn.close()
        except Exception as e:
            pass
        return success


def main():
    cmd_parser = argparse.ArgumentParser(description='Update the WiPy firmware with the specified image file')
    cmd_parser.add_argument('-f', '--file', default=None, help='the path of the firmware file')
    cmd_parser.add_argument('-u', '--user', default='micro', help='the username')
    cmd_parser.add_argument('-p', '--password', default='python', help='the login password')
    cmd_parser.add_argument('--ip', default='192.168.1.1', help='the ip address of the WiPy')
    cmd_parser.add_argument('--verify', action='store_true', help='verify that the update succeeded')
    cmd_parser.add_argument('-t', '--tag', default=None, help='git tag of the firmware image')
    args = cmd_parser.parse_args()

    result = 1

    try:
        if args.file is None:
            raise ValueError('the image file path must be specified')
        if transfer_file(args):
            if reset_board(args):
                if args.verify:
                    print ('Waiting for the WiFi connection to come up again...')
                    # this time is to allow the system's wireless network card to
                    # connect to the WiPy again.
                    time.sleep(5)
                    if verify_update(args):
                        result = 0
                else:
                    result = 0

    except Exception as e:
        print_exception(e)
    finally:
        sys.exit(result)


if __name__ == "__main__":
    main()
