# copyright 2013-2020, IQ2 Development GmbH, Germany. All rights reserved.
# Firmware update example

# requirements:
# iqDll, version >= 1.06
# python, version >= 3.6 (tested on 3.8.1)

# How to use
# Set full path to dll for "comm_dll_path" variable
# Set full path to binary firmware file in "fw_file" variable
# Set correct hardware ID and Password in function mst_FirmwareUpdate. 
# If no password is used, null or empty string should be passed as parameters

import sys, platform
import ctypes, ctypes.util
from ctypes import *
comm_dll_path = "p:\iqDll\DSV_2020FEB06_BLOB_FW\Release\iqcomm.dll"

MST_OPERATING_MODE_INACTIVE   = 0
MST_OPERATING_MODE_AUTO       = 3
MST_OPERATING_MODE_PREOPERATE = 4
MST_OPERATING_MODE_OPERATE    = 5

MST_STATE_INACTIVE    = 0
MST_STATE_CHK_FAULT   = 3
MST_STATE_PREOPERATE  = 4
MST_STATE_OPERATE     = 5

ERR_NONE              =  0
ERR_INTERNAL          =  -1
ERR_NOTFOUND          =  -2
ERR_BUFFER_OVERFLOW   =  -3
ERR_PARAMETER         =  -4
ERR_OPERATION         =  -5
ERR_DATA              =  -6
ERR_CUSTOM            =  -7

try:
    comm_dll = ctypes.CDLL(comm_dll_path)
except OSError:
    print(f"Cannot load: {comm_dll_path}")

#buffer for error messages
cpErrMsg = create_string_buffer(1024)
ucState = create_string_buffer(1)

###############################################################################
# EXPORT DLL function
###############################################################################

# export C function: mst_GetVersion
# /**
#  * \brief Get current DLL version
#  *
#  * \param[in]   p_major        pointer to output parameter: major version
#  * \param[in]   p_minor        pointer to output parameter: minor version
#  * \param[out]  cpErrMsg            an error message (minimum 256 symbols!)
#  *
#  * \return operation result
#  */
# DLL_API
# int16 mst_GetVersion(uint16* p_major, uint16* p_minor, char08 *cpErrMsg);
try:
    mst_GetVersion = comm_dll.mst_GetVersion
    mst_GetVersion.argtypes = [POINTER(c_uint16), POINTER(c_uint16), ctypes.c_char_p]
    mst_GetVersion.restype = ctypes.c_int16
except:
    print(f"Dll version is too old and not supported firmware update")
    raise

# export C function: mst_Connect
mst_Connect = comm_dll.mst_Connect
mst_Connect.argtypes = [ctypes.c_char, ctypes.c_char, ctypes.c_char_p]
mst_Connect.restype = ctypes.c_int16

# export C function: mst_SetOperatingMode
# int16 mst_SetOperatingMode(uint16 uiPortNo,
#                            uchar08 ucOperMode,
#                            uchar08 ucStateToWait,
#                            uchar08 *ucpState,
#                            char08 *cpErrMsg)
mst_SetOperatingMode = comm_dll.mst_SetOperatingMode
mst_SetOperatingMode.argtypes = [ctypes.c_uint16, ctypes.c_char, ctypes.c_char, ctypes.c_char_p, ctypes.c_char_p]
mst_SetOperatingMode.restype = ctypes.c_int16

# export C function: mst_FirmwareUpdate
# /**
#  * \brief Update device firmware
#  *
#  * \param[in]		uiPortNo        COM or virtual ethernet port number of iqInterface
#  * \param[in]		cpHwId          pointer to HW Id string
#  * \param[in]       cpPassword      pointer to Password string
#  * \param[in]       file_buffer     pointer to buffer with firmware update file
#  * \param[in]		bufferSize      buffer size
#  * \param[in]		retries			how many retries to connect after firmware update. 1 Retries = 1 second
#  * \param[out]		cpErrMsg        an error message (minimum 256 symbols!)
#  */
# DLL_API
# int16 mst_FirmwareUpdate(uint16 uiPortNo,
# 	const char* cpHwId,
# 	const char* cpPassword,
# 	uchar08* file_buffer,
# 	ulong32 bufferSize,
# 	ulong32 retries,
# 	char08 *cpErrMsg);
mst_FirmwareUpdate = comm_dll.mst_FirmwareUpdate
mst_FirmwareUpdate.argtypes = [
                                ctypes.c_uint16, #uint16 uiPortNo,
                                ctypes.c_char_p, #const char* cpHwId,
                                ctypes.c_char_p, #const char* cpPassword,
                                ctypes.c_char_p, #uchar08* file_buffer
                                ctypes.c_uint32, #ulong32 bufferSize,
                                ctypes.c_uint32, #ulong32 retries,
                                ctypes.c_char_p  #char08 *cpErrMsg
                                ] 
mst_FirmwareUpdate.restype = ctypes.c_int16                        

############################################################################### 
# Application
###############################################################################

# Check dll version
major = ctypes.c_uint16(0)
minor = ctypes.c_uint16(0)
try:
    error_result = mst_GetVersion(byref(major), byref(minor), cpErrMsg)
    print(f"iqInterface DLL version: {major.value}.{minor.value} \n")

    if ((major.value < 1) or ((major.value == 1) and (minor.value < 6))):
        raise(f"Dll version is too old and not supported firmware update")
except:
    print(f"Dll version is too old and not supported firmware update")
    raise

# Search iqInterface between comport numbers ucPortNoStart and ucPortNoFinish#
# and try to connect it as IO-Link master.
iPortNo = mst_Connect(0, 255, cpErrMsg)
print(f"iqInterface Master found on comport {iPortNo} \n")


# try to set operating mode to Operate
error_result = mst_SetOperatingMode(iPortNo, MST_OPERATING_MODE_OPERATE, MST_STATE_OPERATE, ucState, cpErrMsg)
if (error_result < ERR_NONE):
    raise(f"Cannot change state: {repr(cpErrMsg.value)}")

print(f"state: {repr(ucState.raw)}")

fw_file = "c:\\temp\\IQ2 Development-V1.1.3.0EMULATE_MAXREFDES42-20190307-IOLFW1.0\\fw_application_binary.bin"

with open(fw_file, "rb") as binary_file:
        #Read the whole file at once
        #data = binary_file.read()
        file_array = bytearray(binary_file.read())
        c_byte_array = ctypes.c_char * len(file_array)
        

        error_result = mst_FirmwareUpdate(iPortNo,
                create_string_buffer(b"BOOTLOAD_FW"),
                create_string_buffer(b"1234"),
                c_byte_array.from_buffer(file_array),
                len(file_array),
                3,
                cpErrMsg)
        if (error_result < ERR_NONE):
            raise Exception(f"Cannot execute firmware update: {repr(cpErrMsg.value)}")
        else:
            print(f"Device has been updated successfully")
