## @file  BlueNRG-MS_Firmware_update.py
## @brief This script implements an example of BlueNRG-MS firmware upgrade with ACI commands. You need to specificy the variable fw_filename to point to the wanted firmware image.
## @li The script is implemented using the BlueNRG GUI language scripts based on the BLE ACI APIs names. The BlueNRG GUI with script engine is delivered within BlueNRG GUI SW package.
## @li The script can also be run using the script launcher standalone utility available within BlueNRG GUI SW package.
## A PC running Windows Vista/7 operating systems (64 and 32 bit version) or Windows XP operating systems (32 bit edition), with available USB ports is required for installing the BlueNRG GUI SW package.
##
## <!-- Copyright 2016 by STMicroelectronics.  All rights reserved.       *80*-->
##
##  @section S20 How it works ?
##  The script will check firmware version on BlueNRG-MS and if needed it will update it according to the settings defined by user.
##  The user needs to define the wanted firmware image file (fw_filename), the associated firmware version (fw_version) and the wanted ifr 
##  data (ifr_filename)
##  The script will check whether the firmware inside the device is matching, if yes, it will only update the IFR, otherwise it will 
##  update both firmware and IFR.
##  The memory map of BlueNRG-MS is as follow:
##
##<table border="1">
##<tr>
##<th>      BlueNRG-MS Memory map           </th><th>           Address range            </th></tr><tr>
##<td>       IFR data (192B)                </td><td>        0x10020740 - 0x100207FF     </td> </tr><tr>
##<td>       IFR unused sector (2K - 192)   </td><td>        0x10020000 - 0x1002073F     </td></tr><tr>
##<td>       Fw image (64K)                 </td><td>        0x10010000 - 0x1001FFFF     </td></tr>
##</table>
##
##
## @note BlueFlag is a 32 bit word at address 0x100108C0 and indicate whether a good firmware is present or not.
## It is manipulated by ACI_HAL_UPDATER_ERASE_BLUE_FLAG (make invalid) and 
## ACI_HAL_UPDATER_RESET_BLUE_FLAG() (set valid).
##
## \addtogroup BlueNRG_MS_scripts
## @{
## \see BlueNRG-MS_firmware_update.py for documentation.
## 
## @} 

## @cond DOXYGEN_SHOULD_SKIP_THIS

## User inputs
fw_filename = "c:\\Program Files (x86)\\STMicroelectronics\\BlueNRG GUI 2.2.1\\Firmware\\BlueNRG-MS_stack\\bluenrg_7_2_c_Mode_2-16MHz-XO32K_4M.img"
fw_version="7.2c"
# IFR is included in the .img above, but customers may would like to update it according to their specific board, the example show the good IFR for ST EVal kit.
ifr_filename = "c:\\Program Files (x86)\\STMicroelectronics\\BlueNRG GUI 2.2.1\\Firmware\\BlueNRG-MS_stack\\ifr_3v1_003_mode02-16MHz-XO32K_4M.dat"

## Program constants

SECTOR_SIZE =     2048     # Flash sector size
BASE_ADDRESS =    0x10010000
FW_OFFSET =       0
FULL_STACK_SIZE = (66*1024) # 66 KB (including IFR)
IFR_OFFSET =      (64*1024) # IFR sector will contain both code and IFR data
IFR_DATA_SIZE =   192
BLUEFLAG_OFFSET = 0x8C0

DATA_SIZE =       64        # 64 bytes

def getVersion():
    status,HCI_Version, HCI_Revision, LMP_PAL_Version, Manufacturer_Name, LMP_PAL_Subversion = HCI_READ_LOCAL_VERSION_INFORMATION()
    if status == 0:
        fw_major_version = HCI_Revision & 0xFF
        fw_minor_version = (LMP_PAL_Subversion & 0xF0)>>4
        fw_qualifier_version = (LMP_PAL_Subversion & 0xF)
        if fw_qualifier_version == 0:
            patch_txt = ''
        else:
            # patch is a,b,c...
            patch_txt = chr(96+fw_qualifier_version)
            
        if LMP_PAL_Subversion & 0x8000 :
            dev_txt = 'dev'
        else: 
            dev_txt = ''
        return "%d.%d"%(fw_major_version, fw_minor_version)+patch_txt+dev_txt
    return "Unknown"

# This function will update firmware and IFR in a single step
def updateFirmware(fw_filename, ifr_filename):
    f = open(fw_filename)
    fw_image = []
    for line in f:
        fw_image.append(int(line[6:], 16))
        fw_image.append(int(line[4:6], 16))
        fw_image.append(int(line[2:4], 16))
        fw_image.append(int(line[:2], 16))
    f.close()

    # Read IFR file
    f = open(ifr_filename)
    ifr_data = []
    for line in f:
        ifr_data.append(int(line[6:], 16))
        ifr_data.append(int(line[4:6], 16))
        ifr_data.append(int(line[2:4], 16))
        ifr_data.append(int(line[:2], 16))
    f.close()

    # Take last 192 bytes and add replace them in the firmware image
    fw_image = fw_image[:-IFR_DATA_SIZE] + ifr_data[-IFR_DATA_SIZE:]

    # Patch image to set blue flag to 0xFFFFFFFF
    fw_image[BLUEFLAG_OFFSET]= 0xFF
    fw_image[BLUEFLAG_OFFSET+1]= 0xFF
    fw_image[BLUEFLAG_OFFSET+2]= 0xFF
    fw_image[BLUEFLAG_OFFSET+3]= 0xFF

    # Calculate the number of sectors necessary to contain the fw image.
    number_sectors = ((len(fw_image) + SECTOR_SIZE - 1) / SECTOR_SIZE);

    # Enter bootloader mode
    evt=HW_BOOTLOADER()
    if ((evt.event_code != HCI_VENDOR_EVENT) or
        (evt.get_param('Ecode').val != ACI_BLUE_INITIALIZED_EVENT) or
        ((evt.get_param('Reason_Code').val != 0x04) and
         (evt.get_param('Reason_Code').val != 0x03))):
        ERROR("BlueNRG-MS failed to enter bootloader mode")
        
    # Check updater version
    status, version = ACI_HAL_GET_UPDATER_VERSION()
    if status != 0:
        return "ACI_HAL_GET_UPDATER_VERSION status = %02x"%status
    elif (version < 5):
        return "Unsupported updater version : %d"%version
    
    # Erase blueflag
    status = ACI_HAL_UPDATER_ERASE_BLUE_FLAG()
    if status != 0:
        return "ACI_HAL_UPDATER_ERASE_BLUE_FLAG status = %02x"%status

    # Erase and Program sectors
    for i in range(FW_OFFSET, (number_sectors * SECTOR_SIZE), SECTOR_SIZE):
        #sys.stdout.write(".")
        status = ACI_HAL_UPDATER_ERASE_SECTOR(Address=BASE_ADDRESS + i)
        if (status != 0):
            return "ACI_HAL_UPDATER_ERASE_SECTOR(%08x) status = %02x"%(BASE_ADDRESS + i, status)
        
        for j in range(i, SECTOR_SIZE+i, DATA_SIZE):
            status = ACI_HAL_UPDATER_PROG_DATA_BLK (Address = BASE_ADDRESS+j, Data_Length=DATA_SIZE, Data = fw_image[j:j+DATA_SIZE])
            if (status != 0):
                return "ACI_HAL_UPDATER_PROG_DATA_BLK(%08x) status = %02x"%(BASE_ADDRESS + j, status)
        sys.stdout.write("%.2f%%\r"%(100*(float(i+SECTOR_SIZE)/(number_sectors * SECTOR_SIZE))))
    sys.stdout.write("\n")

    # Reset Blue flag
    status = ACI_HAL_UPDATER_RESET_BLUE_FLAG()
    if (status != 0):
        return "ACI_HAL_UPDATER_PROG_DATA_BLK status = %02x"%status

    ACI_HAL_UPDATER_REBOOT()
    return "OK"

# This function will update just the IFR
def updateIFR(ifr_filename):
    # Read IFR file
    f = open(ifr_filename)
    ifr_data = []
    for line in f:
        ifr_data.append(int(line[6:], 16))
        ifr_data.append(int(line[4:6], 16))
        ifr_data.append(int(line[2:4], 16))
        ifr_data.append(int(line[:2], 16))
    f.close()

    # Take the IFR data only
    ifr_data = ifr_data[-IFR_DATA_SIZE:]

    # Enter bootloader mode
    evt=HW_BOOTLOADER()
    if ((evt.event_code != HCI_VENDOR_EVENT) or
        (evt.get_param('Ecode').val != ACI_BLUE_INITIALIZED_EVENT) or
        ((evt.get_param('Reason_Code').val != 0x04) and
         (evt.get_param('Reason_Code').val != 0x03))):
        ERROR("BlueNRG-MS failed to enter bootloader mode")
        
    # Check updater version
    status, version = ACI_HAL_GET_UPDATER_VERSION()
    if status != 0:
        return "ACI_HAL_GET_UPDATER_VERSION status = %02x"%status
    elif (version < 5):
        return "Unsupported updater version : %d"%version
    
    # Erase and Program IFR sector
    # Erase IFR sector
    status = ACI_HAL_UPDATER_ERASE_SECTOR(Address=BASE_ADDRESS + IFR_OFFSET)
    if (status != 0):
        return "ACI_HAL_UPDATER_ERASE_SECTOR(%08x) status = %02x"%(BASE_ADDRESS + i, status)

    # Program IFR data
    for j in range(SECTOR_SIZE-IFR_DATA_SIZE, SECTOR_SIZE, DATA_SIZE):
        idx=j-(SECTOR_SIZE-IFR_DATA_SIZE)
        status = ACI_HAL_UPDATER_PROG_DATA_BLK (Address = BASE_ADDRESS + IFR_OFFSET + j, Data_Length=DATA_SIZE, Data = ifr_data[idx:idx+DATA_SIZE])
        if (status != 0):
            return "ACI_HAL_UPDATER_PROG_DATA_BLK(%08x) status = %02x"%(BASE_ADDRESS + IFR_OFFSET + j, status)

    ACI_HAL_UPDATER_REBOOT()
    return "OK"

try:
    # Reset BlueNRG-MS
    evt = HW_RESET()
    if (evt.get_param('Reason_Code').val != 0x01):
        PRINT("No firmware detected in the BlueNRG-MS")
        PRINT("ACI_BLUE_INITIALIZED_EVENT code = " + repr(evt.get_param('Reason_Code').val))
        version = None
    else:
        version = getVersion()
except:
    PRINT("HW_RESET Failed no ACI_BLUE_INITIALIZED_EVENT event (Corrupted firmware?)")
    # Corrupted firmware or broken device
    version = None

if (version != fw_version):
    PRINT("UPDATING firmware using img file: %s and IFR file: %s"%(fw_filename, ifr_filename))
    returnValue = updateFirmware(fw_filename, ifr_filename)
    if returnValue != "OK":
        ERROR(returnValue)
else:
    PRINT("Firmware version %s ok, updating only IFR (filename: %s)"%(version, ifr_filename))
    returnValue = updateIFR(ifr_filename)
    if returnValue != "OK":
        ERROR(returnValue)


PRINT("Done")
