## @file OTA_Central.py
## @brief This script is used as component for implementing the Bluetooth Low Energy (BLE) Over-the-Air (OTA) FW upgrade of a BLE device providing the BLE OTA service capability.
## @li The script is implemented using the new BlueNRG GUI language scripts based on the BLE ACI APIs. 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.
## @li 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 S110 OTA_Central.py, preliminary steps: configure a BLE Peripheral device with a application providing the OTA service and related characteristics 
##
##  @li On a BLE device load an application image providing the OTA service (reference example application are BLE Chat and SensorDemo applications with OTA service configuration).
##  @li Make sure application is on discoverable mode and it offers the OTA service capability (refer to related BLE OTA FW upgrade Application Note for more detailed description)
##
##  @section S111  OTA_Central.py script execution steps through BlueNRG GUI Script Engine
##
##  @li Open BlueNRG GUI 
##  @li <b>BlueNRG/BlueNRG-MS platform configuration steps </b>: <ol>
##  <li> Connect the selected platform to a PC USB port
##  <li> Put selected platform in DFU mode and download the "C:\Program Files (x86)\STMicroelectronics\BlueNRG GUI x.x.x\Firmware\STM32L1_prebuilt_images\BlueNRG_VCOM_x_x.hex" using the BlueNRG GUI, Tools, Flash Motherboard FW... utility </ol>
##  @li <b>BlueNRG-1 platform configuration steps</b>: <ol>
##  <li> Connect the selected platform to a PC USB port
##  <li> Download the "C:\Program Files (x86)\STMicroelectronics\BlueNRG GUI x.x.x\Firmware\BlueNRG1\DTM\DTM_UART_16MHz.hex or DTM_SPI.hex" file on the BlueNRG-1 platform using the BlueNRG Flasher utility available on  BlueNRG-1 DK SW package (STSW-BLUENRG1-DK) <ul>
##  <li> <b> NOTES</b>: SPI Network coprocessor configuration file (DTM_SPI.hex) requires some HW changes to be applied to the BlueNRG-1 development platform. Refer to STSW-BNRGUI user manual UM2058. </ul></ol>
##  @li On BlueNRG GUI, open the USB Port associated to the plugged platform
##  @li On BlueNRG GUI, select the Scripts window 
##  @li On BlueNRG GUI, Scripts window, Scripts Engine section, click on ... tab and browse to the OTA_Central.py script location and select it
##  @li On BlueNRG GUI, click on  Run Script tab for running the selected script
##  @li BLE OTA central device searches for BLE Peripheral devices offering the OTA service and it allows user to select the identified device (by pressing the related index key on keyboard)
##  @li Then user is asked to select a binary application image (*.bin file) built with the proper base address (based on the peer device available free flash memory layout) and to start the FW upgrade process
##  @li When the FW upgrade process is completed a specific message is displayed
##  @li NOTE: OTA_Central.py can also be run using the BlueNRG script launcher standalone utility (refer to the related doxygen documentation for instrunctions about how to use it)
##
##  @section S112 Supported platforms
##
##  @li BlueNRG-1 development platform (order code: STEVAL-IDB007V1)
##  @li BlueNRG-MS development platform (order code: STEVAL-IDB005V1) 
##  @li BlueNRG-MS Daughter Board (order code: STEVAL-IDB005V1D)
##  @li BlueNRG-MS USB dongle (order code: STEVAL-IDB006V1)
##  @li BlueNRG development platform (order code: STEVAL-IDB002V1)
##  @li BlueNRG USB dongle (order code: STEVAL-IDB003V1)
##
##  @section S113 Supported BLE devices and stack versions
##
##  @li BlueNRG-1 stack v1.x
##  @li BlueNRG-MS stack v7.2x
##  @li BlueNRG stack v6.4x
##

## \addtogroup BLE_scripts
## @{
## \see OTA_Central.py for documentation.
## 
## @} 

## @cond DOXYGEN_SHOULD_SKIP_THIS

## ------------------------------------------ OTA_Central.py script utilities ---------------------------------------------------------------
_progress_val = -1

def progress(value, total):        
    global _progress_val
        
    new_progress_val = value*100/total
    if new_progress_val != _progress_val:
        _progress_val = new_progress_val
        PRINT(str(_progress_val) +'%')

## Discovery the OTA characteristics handles
def getBtlCharHandle(otaBtlHandle, CharUUID): 

     ACI_GATT_DISC_CHAR_BY_UUID(Connection_Handle=otaBtlHandle,
                                  UUID_Type=0x02,
                                  UUID_128=CharUUID)

     evt = WAIT_EVENT(HCI_VENDOR_EVENT,Ecode=ACI_GATT_DISC_READ_CHAR_BY_UUID_RESP_EVENT)

     respData = evt.get_param('Attribute_Handle').val 

     WAIT_EVENT(HCI_VENDOR_EVENT,Ecode=ACI_GATT_ERROR_RESP_EVENT) 
     evt = WAIT_EVENT(HCI_VENDOR_EVENT,Ecode=ACI_GATT_PROC_COMPLETE_EVENT)

     return respData ##  characteristic attribute handle

## ------------------------------------------ OTA_Central.py script ------------------------------------------------------------------------------------

## BlueNRG FW v6.4
EVENT_TYPE_ADV_IND = 0x00
EVENT_TYPE_SCAN_RSP = 0x04
SERVICE_UUID_LENGHT = 0x12

## 
DEVICE_LIST_DEFAULT_NAME = '(NO-NAME)'
DEBUG_ON = 0 ##0

RESET()
##SET_MODE(2) 
#SET_PUBLIC_ADDRESS(0x123456789CCC)
SET_PUBLIC_ADDRESS()
ACI_HAL_SET_TX_POWER_LEVEL(En_High_Power=1,PA_Level = 4)

if  ACI_GATT_INIT()!=0x00:
    ERROR('ACI_GATT_INIT FAILED')

Status,_,_,_=  ACI_GAP_INIT(Role=CENTRAL)
if (Status != 0x00):
    ERROR('ACI_GAP_INIT CALL FAILED')

deviceList = []
currentPeerAddress = 0

###  First GAP General Discovery Procedure to identify SCAN RESPONSE with OTA Service 128 bits UUID: it stores related device ADV addresses
Status = ACI_GAP_START_GENERAL_DISCOVERY_PROC(LE_Scan_Interval=0x280, 
                                     LE_Scan_Window=0x1E0, 
                                     Own_Address_Type=0x00,
                                     Filter_Duplicates=0x00)

if (Status != 0x00):
    ERROR('ACI_GAP_START_GENERAL_DISCOVERY_PROC CALL FAILED') ## Print error message and stop script execution

#Search bootloader slave candidates
PRINT('Searching for bootloader targets..........')

while (True): 
    
    e = WAIT_EVENT(timeout=30) 

    if e.event_code==HCI_VENDOR_EVENT and e.get_param('Ecode').val==ACI_GAP_PROC_COMPLETE_EVENT:
        break ## exit condition from while True
    
    ## BlueNRG-MS FW v7.1x and later  returns HCI_LE_ADVERTISING_REPORT_EVENT
    elif e.event_code==HCI_LE_META_EVENT and e.get_param('Subevent_Code').val==HCI_LE_ADVERTISING_REPORT_EVENT:

        evtData = e.get_param('Data').get_list()[0] 
        event_type = e.get_param('Event_Type').val[0]

        if (DEBUG_ON == 1):
            PRINT('---------------evtData, event_type')
            PRINT(str(evtData) + str(event_type))

        if ((event_type == 0x04) and (type(evtData) != int) and (IS_OTA(evtData))):  
            newAddress = e.get_param('Address').val[0]
            newAddressType = e.get_param('Address_Type').val[0]  

            if (DEBUG_ON == 1):
                PRINT('---------------newAddress, newAddressType')
                PRINT(str(newAddress) + str(newAddressType))
            if not([newAddress,newAddressType,DEVICE_LIST_DEFAULT_NAME] in deviceList):         
                if (DEBUG_ON == 1):
                    PRINT(str(newAddress) + str(newAddressType))
                deviceList.append([newAddress,newAddressType,DEVICE_LIST_DEFAULT_NAME])

    ## BlueNRG FW v6.4 returns ACI_GAP_DEVICE_FOUND_EVENT
    elif e.event_code==HCI_VENDOR_EVENT and e.get_param('Ecode').val==ACI_GAP_DEVICE_FOUND_EVENT:
         if (e.get_param('Event_Type').val == EVENT_TYPE_SCAN_RSP):

             if (DEBUG_ON == 1):
                print "SCAN_RSP"

             service_uuid_128bits_lenght =  e.get_param('Length_Data').val
             if (service_uuid_128bits_lenght == SERVICE_UUID_LENGHT):
                 service_uuid_128bits = e.get_param('Data').get_list()
                 if (DEBUG_ON == 1):
                     for i in range (service_uuid_128bits_lenght):
                         PRINT(str(hex(service_uuid_128bits[i])))

                 if IS_OTA(service_uuid_128bits):
                     scan_rsp_device_address_type= e.get_param('Address_Type').val
                   
                     device_address = e.get_param('Address').val
                     if (DEBUG_ON == 1):
                         PRINT('scan_rsp_device_address_type ' +  str(hex(scan_rsp_device_address_type)))

                         PRINT('[' + str(hex(device_address)) + ', ' + str(scan_rsp_device_address_type) + ', ' + str(DEVICE_LIST_DEFAULT_NAME) +']')

                     if not([device_address,scan_rsp_device_address_type,DEVICE_LIST_DEFAULT_NAME] in deviceList):
                         deviceList.append([device_address,scan_rsp_device_address_type,DEVICE_LIST_DEFAULT_NAME]) #Latest

if (DEBUG_ON == 1):
    PRINT('-------- SCAN_RSP results:')
    PRINT(str(deviceList))       

if (len(deviceList) > 0) : 
    #look at advertisements in order to get application names
    PRINT('Checking application names...')

    # go ahead to search for device names#
    # Set parameters to scan for advertisements only  

    ### Second GAP General Discovery Procedure for looking for device names of stored devices with OTA Service 128 bits UUID: it stores related ADV device name
    Status = ACI_GAP_START_GENERAL_DISCOVERY_PROC(LE_Scan_Interval=0x280,
                                                  LE_Scan_Window=0x1E0, 
                                                  Own_Address_Type=0x00,
                                                  Filter_Duplicates=0x00)

    if (Status != 0x00):
        ERROR('ACI_GAP_START_GENERAL_DISCOVERY_PROC CALL FAILED') ## Print error message and stop script execution

    while (True):
        e = WAIT_EVENT(timeout=30) 

        if e.event_code==HCI_VENDOR_EVENT and e.get_param('Ecode').val==ACI_GAP_PROC_COMPLETE_EVENT:
            break ## exit condition from while True    
    
        ## BlueNRG-MS FW v7.1x and later returns HCI_LE_ADVERTISING_REPORT_EVENT
        elif (e.event_code==HCI_LE_META_EVENT and e.get_param('Subevent_Code').val==HCI_LE_ADVERTISING_REPORT_EVENT):

            if (e.get_param('Event_Type').val[0] == 0x00):
                advAddress=e.get_param('Address').val[0]
                currentItemIndex = 0

                if (advAddress in [x[0] for x in deviceList]) :
                    deviceListIndex = ([x[0] for x in deviceList].index(advAddress))
                    if (deviceList[deviceListIndex][2] == DEVICE_LIST_DEFAULT_NAME) :

                        adv_ind_device_name=GET_NAME(e.get_param('Data').val[0]) 
                        deviceList[deviceListIndex][2] = adv_ind_device_name 

        ## BlueNRG FW v6.4 returns ACI_GAP_DEVICE_FOUND_EVENT
        elif e.event_code==HCI_VENDOR_EVENT and e.get_param('Ecode').val==ACI_GAP_DEVICE_FOUND_EVENT:    
        ## CHECK FOR ADV_IND
            if  (e.get_param('Event_Type').val ==  EVENT_TYPE_ADV_IND):
                if (DEBUG_ON == 1):
                    PRINT('ADV_IND')
                    adv_ind_device_address_type  = e.get_param('Address_Type').val

                device_address =  e.get_param('Address').val 
                currentItemIndex = 0

                if (device_address in [x[0] for x in deviceList]) :
                    deviceListIndex = ([x[0] for x in deviceList].index(device_address))
                    if (deviceList[deviceListIndex][2] == DEVICE_LIST_DEFAULT_NAME) :

                        adv_ind_device_name=GET_NAME(e.get_param('Data').val) 
                        deviceList[deviceListIndex][2] = adv_ind_device_name             

if (len(deviceList) == 0):
    currentPeerAddress = 0
    ERROR('No devices running OTA bootloader service were found') 

else: 
    PRINT('Available OTA peer device candidates:')
    count = 0
    for elem in deviceList:
        if (DEVICE_LIST_DEFAULT_NAME not in elem):
           PRINT(str(count) + ': ' + '[' + str(hex(elem[0])) + ', ' + str(elem[1]) + ', ' + str(elem[2]) + ']')
           count +=1

index = GET_CHAR('Select one index of the available OTA peer devices. Press related key: [0 - ' + str(count-1) + ']')

if (index == None):
    ERROR("No index has been selected")

num = int(index) ## GET_CHAR returns the inserted character

INFO('Press OK for connecting to peer device!  Address: ' +str(hex(deviceList[num][0])) + ', Address type: ' + str(deviceList[num][1]) + ', Name: ' + str(deviceList[num][2]))

### Connect to selected the device with OTA service 
currentPeerAddress = deviceList[num][0]
currentPeerAddressType = deviceList[num][1]
PRINT('Selected device: ' + str(hex(deviceList[num][0])))

Status = ACI_GAP_CREATE_CONNECTION(LE_Scan_Interval=0x280,
                                   LE_Scan_Window=0x1E0, 
                                   Peer_Address_Type=currentPeerAddressType, 
                                   Peer_Address=currentPeerAddress,
                                   Own_Address_Type=0, 
                                   Conn_Interval_Min=0x0006,
                                   Conn_Interval_Max=0x0006,
                                   Conn_Latency=0,   
                                   Supervision_Timeout=0x0C80,
                                   Minimum_CE_Length=0x10, 
                                   Maximum_CE_Length=0xFFFF)
       
if Status !=0x00:
    ERROR('ACI_GAP_CREATE_CONNECTION CALL FAILED') 

WAIT_EVENT(HCI_VENDOR_EVENT,timeout=30,Ecode=ACI_GAP_PROC_COMPLETE_EVENT)

e= WAIT_EVENT(HCI_LE_META_EVENT,timeout=30,Subevent_Code=HCI_LE_CONNECTION_COMPLETE_EVENT)
if e.get_param('Status').val==0x00:
    otaBtlHandle = (e.get_param('Connection_Handle').val)
    if (otaBtlHandle == None) or (otaBtlHandle > 0x0EFF ) :
          currentPeerAddress = 0
          ERROR('Connection handle out of range')
         
    else :
        #PRINT('Connection_Handle: ' + str(hex(otaBtlHandle)))
        currentDeviceIndex = num 


### Read OTA service characteristics handles
BTLCharHandle1=getBtlCharHandle(otaBtlHandle,0x122e8cc0850811e3baa70800200c9a66)
if (DEBUG_ON == 1):
    PRINT('BTL char handle1: ' + hex(BTLCharHandle1))
BTLCharHandle2=getBtlCharHandle(otaBtlHandle,0x210f99f0850811e3baa70800200c9a66)
if (DEBUG_ON == 1):
    PRINT('BTL char handle2: ' + hex(BTLCharHandle2))
BTLCharHandle3=getBtlCharHandle(otaBtlHandle,0x2691aa80850811e3baa70800200c9a66)
if (DEBUG_ON == 1):
    PRINT('BTL char handle3: ' + hex(BTLCharHandle3))
BTLCharHandle4=getBtlCharHandle(otaBtlHandle,0x2bdc5760850811e3baa70800200c9a66)
if (DEBUG_ON == 1):
    PRINT('BTL char handle4: ' + hex(BTLCharHandle4))

### Read free memory information from related OTA characteristic 
Status = ACI_GATT_READ_CHAR_VALUE(Connection_Handle=otaBtlHandle,
                                          Attr_Handle=BTLCharHandle1+1)
if (Status != 0x00):
    ERROR('ACI_GATT_READ_CHAR_VALUE(Attr_Handle=' + hex(BTLCharHandle1+1) + ' CALL FAILED')
evt = WAIT_EVENT(HCI_VENDOR_EVENT,2,Ecode=ACI_ATT_READ_RESP_EVENT) 

notifData = (evt.get_param('Attribute_Value').get_list()) 

WAIT_EVENT(HCI_VENDOR_EVENT,Ecode=ACI_GATT_PROC_COMPLETE_EVENT)

# Store peer free flash range: [LB, UB]
flash_LB=(notifData[0]<<24)+(notifData[1]<<16)+(notifData[2]<<8)+notifData[3]  
flash_UB=(notifData[4]<<24)+(notifData[5]<<16)+(notifData[6]<<8)+notifData[7] 

## Set base address 
base_address = flash_LB

### Peer Device Flash Update with new selected FW image 

filename = GET_FILE(msg = 'Add new image file path with required base address: ' + str(hex(base_address)))
PRINT('Selected filename: ' + filename)

INFO('Press OK for updating Peer Device FW from address ' +hex(flash_LB))

try: 
    f = open(filename,'rb')
    try: 

        fw_image16 = []
        #we loop to upload image in advance and count for size rather than working with openfile and relying on os.path.getsize(filename)
        cnt=0
        imageWord16b = 0
        while True:
           byte = f.read(1) 
           if byte != "":
               imageWord16b += (ord(byte) << ((cnt%16)*8))
               if ((cnt+1)%16 == 0) :
                   fw_image16.append(imageWord16b)
                   imageWord16b = 0
               cnt+=1

           else:
               if ((cnt%16)!=0) :
                   fw_image16.append(imageWord16b)

               if (DEBUG_ON == 1): ## (TBR)
                   PRINT('image size is: ' + str(cnt))
                   PRINT('16 byte wise size: ' + str(len(fw_image16)))

                   PRINT('base_address, ' + str(hex(base_address)))
                   PRINT('flash_LB, ' + str(hex(flash_LB)))
                   PRINT('flash_UB, ' + str(hex(flash_UB)))

               if (IS_BlueNRG() or IS_BlueNRG_MS()):
                   page_alignment = 512
               else:
                   page_alignment = 2048
               if ((base_address < flash_LB) or ((base_address+cnt) > flash_UB) or ((base_address%page_alignment)!=0)): 
                  
                   ERROR('Image above free memory range or base address is not aligned')
    
               break
    finally:      
        f.close()       
    
    if IS_BlueNRG():
        OTA_ACK_EVERY = 1  ## BLUENRG value (allowed values: 1; 1 notification after 1 write_without_response)
    else:
        OTA_ACK_EVERY = 8  ## BLUENRG-MS value (allowed values: 8; 1 notification after 8 write_without_response)

    btlBoundaries =  (base_address<<40) + (cnt << 8) + OTA_ACK_EVERY

    retries = 0
   
    ACI_GATT_WRITE_CHAR_VALUE(Connection_Handle=otaBtlHandle,
                              Attr_Handle=BTLCharHandle2+1,
                              Attribute_Val_Length=0x09,
                              Attribute_Val=btlBoundaries)
    evt = WAIT_EVENT(HCI_VENDOR_EVENT,Ecode=ACI_GATT_PROC_COMPLETE_EVENT)      

    ACI_GATT_READ_CHAR_VALUE(Connection_Handle=otaBtlHandle,
                             Attr_Handle=BTLCharHandle2+1)

    evt = WAIT_EVENT(HCI_VENDOR_EVENT,Ecode=ACI_ATT_READ_RESP_EVENT)
    notifData = evt.get_param('Attribute_Value').get_list()  
    verifData = 0 
    notifData.reverse()

    for elem in (notifData[:len(notifData)]) : 

        verifData<<=8
        verifData += elem

    if (btlBoundaries != verifData) :
      retries += 1
      if (retries > 10) :
          ERROR('Cannot write parameters for OTA image transfer :')
    elif (DEBUG_ON == 1):
      PRINT('OTA image transfer parameters written')

    ACI_GATT_WRITE_CHAR_DESC(Connection_Handle=otaBtlHandle,
                                     Attr_Handle= BTLCharHandle4+2,
                                     Attribute_Val_Length=0x01,
                                     Attribute_Val=0x01)

    evt = WAIT_EVENT(HCI_VENDOR_EVENT, Ecode=ACI_GATT_NOTIFICATION_EVENT, continueOnEvtMiss=True, timeout =2) 

    characSize = 16
    roundedImgSize = (len(fw_image16) * characSize) 
    SeqNum = 0
    retriesForChesumError = 0
    retriesForSequenceError = 0
    retriesForMissedNotification = 0
    retriesOnSPI = 0
    consecutiveRetries = 0

    Expected_Notification_Window = 0
    
    while ((SeqNum * characSize) < roundedImgSize):
        checksum = 0

        prevSeqNum = SeqNum
        needsACK =  (((Expected_Notification_Window+1)%(OTA_ACK_EVERY)) == 0) or (((SeqNum+1) * characSize) == roundedImgSize)
        ImgPayload = (SeqNum<<144) + ((int(needsACK))<<136) + (fw_image16[SeqNum] << 8)
        for k in range(19):
            checksum ^= ((ImgPayload >> (8*(k+1))) & 0xFF)

        CheckedImgPayload = ImgPayload + checksum
        if (DEBUG_ON == 1): 
            PRINT('***********************************************')
            PRINT('SeqNum: ' + str(hex(SeqNum)))
            PRINT('needs Ack: ' + str(hex(needsACK)))
            PRINT('payload: ' + str(hex(CheckedImgPayload)))
            PRINT('checksum: ' + str(hex(checksum)))
        while True:
            if (prevSeqNum == SeqNum):
               consecutiveRetries+=1
            else :
               consecutiveRetries=0

            evt = ACI_GATT_WRITE_WITHOUT_RESP(Connection_Handle=otaBtlHandle,
                                                  Attr_Handle=BTLCharHandle3+1,
                                                  Attribute_Val_Length=0x0014,
                                                  Attribute_Val= CheckedImgPayload)
            if evt != 0:
                retriesOnSPI+=1
                if (OTA_ACK_EVERY > 1) :
                  #Prevent retries occurring too often (?)  
                  SLEEP(0.1) 
            else:
                if (retries >= (len(fw_image16)/3)):
                    ERROR('Too many errors, aborting OTA bootloading..')
                else:
                    break

        ## A write without response has been done: increase the range for reaching the expected notification limit
        Expected_Notification_Window +=1    
    
        prevSeqNum=SeqNum
        if needsACK:
            evt = WAIT_EVENT(HCI_VENDOR_EVENT,timeout = 0.1,continueOnEvtMiss=True,Ecode=ACI_GATT_NOTIFICATION_EVENT) 
            if (evt!=None):  
                notifData = evt.get_param('Attribute_Value').get_list()  
                index = 0

                nextExpectedCharBlock = (notifData[index] + (notifData[index+1]<<8))  
                if (notifData[index+2] == 0xFF):
                    ERROR('FLASH WRITE FAILED ON TARGET DEVICE: Repeat FW upgrade procedure')
                elif (notifData[index+2] == 0x3C) :
                    ERROR('FLASH VERIFY FAILED ON TARGET DEVICE: Repeat FW upgrade procedure')
                elif (notifData[index+2] == 0x0F):
                    SeqNum = nextExpectedCharBlock
                    retriesForChesumError+=1
                elif (notifData[index+2] == 0xF0):
                    SeqNum = nextExpectedCharBlock
                    retriesForSequenceError+=1
                else:
                    SeqNum = nextExpectedCharBlock
                    progress(min((SeqNum * 16),cnt),cnt)
            elif (((SeqNum+1) * characSize) == roundedImgSize):
                     progress((SeqNum+1) * characSize,cnt)    
                     break        
            else:
                if (DEBUG_ON == 1):
                    PRINT('Missed notification, retrying last sequence')
                SeqNum-=(OTA_ACK_EVERY - 1)
                retriesForMissedNotification+=1
        else:
            SeqNum+=1 

    PRINT('OTA BLE FW upgrade completed !')
      
    if len(deviceList) > 0 :
        currentPeerAddress = 0
        deviceList.pop(currentDeviceIndex)
      
    ret = ACI_GAP_TERMINATE(Connection_Handle=otaBtlHandle, Reason=0x13)
   
    PRINT('FW UPGRADE COMPLETED')

except Exception as e:
    PRINT('BTL procedure warning !!!' + str(e))
    
     

## @endcond
