"""
Binho Supernova Python package

This Python package is developed to control the new Supernova USB Host adapter.
The new Binho Supernova device is an USB HID device that is controlled using a set
of USB commands that are transferred over USB HID Interrupt transfers.
"""
from .import NXP_VID, LPC5536_PID

# Import drivers.
import hid
from serial import *

# Import services (Threads).
from .services.usb_receiver import UsbReceiver

# Import USB HID commands library.
from .commands.definitions import *
from .commands.validators import *

# Import other modules
from .utils.system_message import *
import inspect
import functools

class Supernova:
    """
    This class represents the Binho Supernova device.

    It involves the drivers used to communicate with the device (USB HID and Serial Port),
    and the different processes that control the commands request and responses.

    Attributes
    ----------
    usbHidDevice : Device
        This attribute offers the communication with the USB device over USB at the bottom layer.
    usbRx : UsbReceiver
        Service dedicated to receives the messages from the USB device, using the usbHidDevice attribute.
    """

    # Private method ----------------------------------------------------------------------------------

    def __sendMessage(self, request_metadata) -> None:
        """
        This method is used to send a new message via USB. The method updates the USB receiver with the transaction information (ID and response), 
        and sends all the needed packages via USB to the Supernova.
        """
        # Update the receiver on this new message
        self.usbRx.addTransactionResponse(request_metadata["id"],request_metadata["response"])
        # Send message to the Supernova
        self.__sendUSBPackages(request_metadata["requests"])

    def __sendUSBPackages(self, usbTransfers):
        """
        This method sends all the USB packages to the Supernova
        """
        for transfer in usbTransfers:
            # Send request to USB host adapter.
            self.usbHidDevice.write(bytes(transfer.data))

    def __ensureProperInitialization(self)-> bool:
        """
        This method checks if the supernova is opened and the on_event callback is set
        to indicate if the functionalities can be accessed or not.

        Returns
        -------
        bool
            True if functionalities are allowed, and False if they aren't
        """
        return (self.usbHidDevice is not None) and (self.usbRx is not None) and (self.response_callback is not None)
    
    def __checkFunctionalities(func):
        """
        Decorator used to verify that the Supernova is correctly configured before using its functionalities
        """
        def wrapper(self, *args, **kwargs):
            if not(self.__ensureProperInitialization()):
                sys_response = SystemMessage(SystemModules.SYSTEM, SystemOpcode.CONFIGURATION_ERROR, "Supernova not configured appropriately, please check if connection is open and callback is set")
                return sys_response.toDictionary()
            return func(self, *args, **kwargs)
        return wrapper    

    def __init__(self):
        """
        Constructor defined to add the Supernova attributes and initialize them as None.
        """
        # HID device used to connect to the supernova via USB
        self.usbHidDevice = None
        # USB receiver
        self.usbRx = None
        # Callback used to handle responses
        self.response_callback = None
 
    # Public methods ----------------------------------------------------------------------------------

    def open(self, vid=NXP_VID, pid=LPC5536_PID, serial = None, path:str = None) -> dict:
        """
        This method establishes the USB communication with the Supernova. When this method is invoked, the USB Host starts to send USB frames to the
        USB device, and to pole the Input endpoint.

        It is possible to connect to the USB device only with the VID and PID. Optionally, the serial number can be passed as argument.

        On the contrary, the path string can be used to identify every the device, so passing the path only is enough to connect with
        the desired device. To get the device path, the BinhoSupernova.getConnectedSupernovaDevicesList() method can be invoked.

        Parameters
        ----------
        vid : int
            USB device VID number. By default the NXP VID number is used.

        pid : int
            USB device PID number. By default the Binho Supernova PID number is used.

        serial : int
            USB device serial number. By default is None.

        path : str
            String path generated by the OS that can be read using the BinhoSupernova.getConnectedSupernovaDevicesList() method.

        Returns
        -------
        dict
            The SystemMessage in dictionary format.
        """

        # Create response instance.
        sys_response = SystemMessage(SystemModules.SYSTEM, SystemOpcode.OK, "Connection with Supernova device opened successfully.")

        # Connect to a Supernova device handling exceptions.
        try:

            if self.usbHidDevice is None:
                # Instance of USB HID device
                self.usbHidDevice = hid.device()
                if path:
                    path_bytes = path.encode(encoding='utf-8')
                    self.usbHidDevice.open_path(path_bytes)
                else:   
                    self.usbHidDevice.open(vid, pid, serial)

            if self.usbRx is None:
                # USB HID Rx thread.
                self.usbRx = UsbReceiver(self.usbHidDevice, 64)
                self.usbRx.start()
                # Callback defined but not set
                if self.response_callback is not None:
                    self.usbRx.setOnEventCallback(self.response_callback)

        except Exception as exc:
            sys_response.opcode = SystemOpcode.OPEN_CONNECTION_FAIL
            sys_response.message = f"Open connection failed. Exception type: {type(exc)}. Exception message: {exc}."

        return sys_response.toDictionary()

    def close(self) -> dict:
        """
        This method closes the communication with the Supernova and releases the used memory.
        
        Returns
        -------
        dict
            The SystemMessage in dictionary format.
        """
        
        sys_response = SystemMessage(SystemModules.SYSTEM, SystemOpcode.OK, "Communication closed successfully.")
        if (self.usbHidDevice is not None) and (self.usbRx is not None):
            # End USB RX process.
            self.usbRx.endProcess()
            # Wait for the usbRX to end
            self.usbRx.thread.join()
            # Close HID device.
            self.usbHidDevice.close()
        else:
            sys_response.code = SystemOpcode.OPEN_CONNECTION_REQUIRED
            sys_response.message = "It is required to open connection with a Supernova first. Invoke open() method."

        return sys_response.toDictionary()

    def onEvent(self, callback_func) -> dict:
        """
        This method registers the callback invoked every time a new USB response or notification
        arrives from the USB device. The callback function must implement the following signature:

            def callback(supernova_response, system_message) -> None:

        It's important to note that the callback is called directly. If the callback function
        introduces a delay, it can impact the receiving mechanism. Developers using this SDK are
        advised to implement a queuing mechanism to quickly liberate the SDK as soon as the callback
        is called. This approach helps in managing responses efficiently without blocking the SDK's
        processing capabilities.

        Parameters
        ----------
        callback_funct : function
            Callback function that will be invoked every time a new USB response or notification is
            sent by the USB device.

        Returns
        -------
        dict
            The SystemMessage in dictionary format.
        """

        sys_response = SystemMessage(SystemModules.SYSTEM, SystemOpcode.OK, "On event callback function registered successfully.")

        # Get the function signature
        func_signature = inspect.signature(callback_func)

        # Get the parameters of the function
        func_parameters = func_signature.parameters

        # Check if the function has exactly 2 parameters
        if len(func_parameters) != 2:
            sys_response.code = SystemOpcode.INVALID_CALLBACK_SIGNATURE
            sys_response.message = "The function must accept 2 arguments: callback(supernova_response, system_message)."
        else:
            # Save the callback function
            self.response_callback = callback_func
            # Set the callback function to the USB Rx if created
            if (self.usbRx is not None):
                # Set the callback function to the USB Rx
                self.usbRx.setOnEventCallback(callback_func)

        return sys_response.toDictionary()

    # -----------------------------------------------------------------------------------
    # Communication API - USB commands
    # -----------------------------------------------------------------------------------

    # Get USB strings -------------------------------------------------------------------
    @__checkFunctionalities
    def getUsbString(self, id: int, subCommand : GetUsbStringSubCommand) -> dict:

        """
        This function sends a Get USB String command taking the subcommand
        passed as parameter. The list of subcommands:

        GetUsbStringSubCommand.MANUFACTURER - Returns the manufacturer string ("Binho").
        GetUsbStringSubCommand.PRODUCT_NAME - Returns the product name string ("Binho Supernova").
        GetUsbStringSubCommand.SERIAL_NUMBER - Returns the product serial number. Up to 32 characters.
        GetUsbStringSubCommand.FW_VERSION - Returns the product firmware version (VX.X.X).
        GetUsbStringSubCommand.HW_VERSION - Returns the product hardware version (HW-X).
        GetUsbStringSubCommand.BT_VERSION - Returns the product bootloader version (VX.X.X).

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The range allowed is [0, 65535].

        subCommand : GetUsbStringSubCommand
            Subcommand that indicates what string must be retrieved.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : GET_USB_STRING,
            "subcommand" : subCommand
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = getUsbStringValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response 
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    # Set I3C Bus Voltage strings -------------------------------------------------------------------
    @__checkFunctionalities
    def setI3cBusVoltage(self, id: int, i3cBusVoltage : c_uint16) -> dict:
        """
        This function sends a SET_I3C_BUS_VOLTAGE command taking desired LDO output voltage (in mV) as parameter.
        
        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        i3cBusVoltage : c_uint16   
            This voltage determines two function modes:
            1 - I3C_LOW_VOLTAGE_MODE that allows voltages in the range [800, 1200) mV
            2 - I3C_STANDARD_VOLTAGE_MODE that allows voltages in the range [1200, 3300] mV

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.  
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : SET_I3C_BUS_VOLTAGE,
            "i3cBusVoltage" : i3cBusVoltage
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = setI3cBusVoltageValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response 
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def resetDevice(self, id: int) -> dict:
        """
        This function sends a RESET DEVICE command.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.  
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : RESET_DEVICE
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = resetDeviceValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response 
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Wait for the usbRX to end
        self.usbRx.thread.join()

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def enterBootMode(self, id: int) -> dict:
        """
        This function sends a ENTER BOOT MODE command.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.  
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : ENTER_BOOT_MODE
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = enterBootModeValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response 
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Wait for the usbRX to end
        self.usbRx.thread.join()

        # Return result in dict format.
        return result.toDictionary()

    # Set I2C SPI UART Bus Voltage strings -------------------------------------------------------------------

    @__checkFunctionalities
    def setI2cSpiUartBusVoltage(self, id: int, i2cSpiUartBusVolt : c_uint16) -> dict:
        """
        This method sends a SET_I2C_SPI_UART_BUS_VOLTAGE command taking desired bus voltage

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The allowed range is [0, 65535].

        i2cSpiUartBusVolt : c_uint16
            It is a 2-bytes integer that indicates the I2C, SPI and UART bus operating voltage, The allowed range is [1200, 3300] mV.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : SET_I2C_SPI_UART_BUS_VOLTAGE,
            "i2cSpiUartBusVolt" : i2cSpiUartBusVolt,
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = setI2cSpiUartBusVoltValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    # I2C management --------------------------------------------------------------------
    
    @__checkFunctionalities
    def i2cSetParameters(self, id: int, cancelTransfer = 0x00, baudrate = 0x00) -> dict:
        """
        This method sets the I2C transfers baudrate and allows to cancel a current transfer too.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The range allowed is [0, 65535].

        cancelTransfer : byte
            It is a subcommand that allows to cancel the last I2C transfer if it is still running. This
            parameter has to possible values:

            - 0x00: Don't cancel current transfer.
            - 0x01: Cancel current transfer.

        baudrate : int
            This parameter represents the I2C SCL frequency in Hz. Currently, the maximum allowed value is
            1000000 that corresponds to 1 MHz.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I2C_SET_PARAMETERS,
            "cancelTransfer" : cancelTransfer,
            "baudrate" : baudrate
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i2cSetParametersValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i2cWrite(self, id: int, slaveAddress : int, registerAddress: list, data: list) -> dict:
        """
        This method is used to request to the the Supernova device to perform an I2C write transfer. The
        I2C write transfer starts with a START condition and ends with a STOP condition.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The allowed range is [0, 65535].

        slaveAddress : byte
            I2C slave static address.

        registerAddress : list
            Python list that contains the memory/register address of the I2C slave internal memory, whose data
            will be written. The list holds bytes, and can hand hold from 0 bytes up to 4 bytes. 0 bytes means
            that the list can be left empty and the Supernova will ignore it and write only the data payload.

        data : list
            Python list that contains the I2C data transferred in the I2C Write transfer. The list holds
            bytes elements, and the maximum length is 1024 bytes.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I2C_WRITE,
            "slaveAddress" : slaveAddress,
            "registerAddress" : registerAddress,
            "data" : data
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i2cWriteValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i2cWriteNonStop(self, id: int, slaveAddress : int, registerAddress: list, data: list) -> dict:
        """
        This method is used to request to the the USB device to perform an I2C write transfer that
        starts with a START condition but the STOP condition is not performed after the last byte.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The allowed range is [0, 65535].

        slaveAddress : byte
            I2C slave static address.

        registerAddress : list
            Python list that contains the memory/register address of the I2C slave internal memory, whose data
            will be written. The list holds bytes, and can hand hold from 0 bytes up to 4 bytes. 0 bytes means
            that the list can be left empty and the Supernova will ignore it and write only the data payload.

        data : list
            Python list that contains the I2C data transferred in the I2C Write transfer. The list holds
            bytes elements, and the maximum length is 1024 bytes.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I2C_WRITE_NO_STOP,
            "slaveAddress" : slaveAddress,
            "registerAddress" : registerAddress,
            "data" : data
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i2cWriteNonStopValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i2cRead(self, id: int, slaveAddress: int, requestDataLength: int) -> dict:
        """
        This method is used to request to the the USB device to perform an I2C read transfer. The
        I2C read transfer starts with a START condition and ends with a STOP condition.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The allowed range is [0, 65535].

        slaveAddress : byte
            I2C slave static address.

        requestDataLength : int
            Length of the read data. The maximum value is 1024 bytes.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """

        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I2C_READ,
            "slaveAddress" : slaveAddress,
            "registerAddress" : None,
            "dataLength" : requestDataLength
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i2cReadValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i2cReadFrom(self, id: int, slaveAddress: int, registerAddress: list, requestDataLength: int) -> dict:
        """
        This method is used to request to the the USB device to perform an I2C read transfer, performing
        before an I2C write transfer to send the register/memory address the data will be read from. A R-Start condition
        is performed between the I2C write and I2C read transfers.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The allowed range is [0, 65535].

        slaveAddress : byte
            I2C slave static address.

        registerAddress : list
            Python list that contains the memory/register address of the I2C slave internal memory, whose data
            will be read. The list holds bytes, and can hand hold from 0 bytes up to 4 bytes. 0 bytes means
            that the list can be left empty, the Supernova will ignore it and will perform only a read transfer.

        requestDataLength : int
            Length of the read data. The maximum value is 1024 bytes.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """

        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I2C_READ_FROM,
            "slaveAddress" : slaveAddress,
            "registerAddress" : registerAddress,
            "dataLength" : requestDataLength
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i2cReadFromValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    # I3C management --------------------------------------------------------------------

    @__checkFunctionalities
    def i3cControllerInit(self, id: c_uint16) -> dict:
        '''
        This function is in charge of initializing the I3C peripheral of the Supernova as a controller.

        Arguments
        ---------
        id : c_uint16
                An integer number that identifies the transfer.

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        
        '''

        metadata = {
            "id" : id,
            "command" : I3C_CONTROLLER_INIT
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cControllerInitValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
        
    @__checkFunctionalities
    def i3cInitBus(self, id: int, targetDeviceTable: dict = None) -> dict:
        """
        This method is used to Initialize the I3C bus for communication with I3C devices.

        Parameters
        ----------
        id : int
            A 2-byte integer representing the initialization ID. The range allowed is [0, 65535].

        targetDeviceTable : dict, optional
            A dictionary containing information about the target devices connected to the I3C bus.
            The default value is None.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        metadata = {
            "id" : id,
            "command" : I3C_INIT_BUS,
            "targetDeviceTable" : targetDeviceTable
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cInitBusValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cGetTargetDeviceTable(self, id: int) -> dict:
        """
        This method retrieves the target device table information from the I3C bus.

        Parameters
        ----------
        id : int
            A 2-byte integer representing the request ID. The range allowed is [0, 65535].

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        -------
        Note: The retrieved PID for each target is in MSB order
        """
        
        metadata = {
            "id" : id,
            "command" : I3C_GET_TARGET_DEVICE_TABLE
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cGetTargetDeviceTableValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cGetCapability(self, id: int) -> dict:
        """
        This method retrieves the capabilities of the I3C bus.

        Parameters
        ----------
        id : int
            A 2-byte integer representing the request ID. The range allowed is [0, 65535].

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        metadata = {
            "id" : id,
            "command" : I3C_GET_CAPABILITY
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cGetCapabilityValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cClearFeature(self, id: int, selector: I3cClearFeatureSelector, targetAddress: c_uint8) -> dict:
        """
        This method clears a specific feature for a target device on the I3C bus.

        Parameters
        ----------
        id : int
            A 2-byte integer representing the request ID. The range allowed is [0, 65535].

        selector : I3cClearFeatureSelector
            An enumeration representing the feature to be cleared.

        targetAddress : c_uint8
            The address of the target device whose feature is to be cleared.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_CLEAR_FEATURE,
            "selector" : selector,
            "targetAddress" : targetAddress
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cClearFeatureValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cSetFeature(self, id: int, selector: I3cSetFeatureSelector, targetAddress: c_uint8) -> dict:
        """
        This method sets a specific feature for a target device on the I3C bus.

        Parameters
        ----------
        id : int
            A 2-byte integer representing the request ID. The range allowed is [0, 65535].

        selector : I3cSetFeatureSelector
            An enumeration representing the feature to be set.

        targetAddress : c_uint8
            The address of the target device whose feature is to be set.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_SET_FEATURE,
            "selector" : selector,
            "targetAddress" : targetAddress
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cSetFeatureValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def i3cChangeDynamicAddress(self, id: int, currentAddress: c_uint8, newAddress: c_uint8) -> dict:
        """
        This method changes the dynamic address of a device on the I3C bus.

        Parameters
        ----------
        id : int
            A 2-byte integer representing the request ID. The range allowed is [0, 65535].

        currentAddress : c_uint8
            The current dynamic address of the device.

        newAddress : c_uint8
            The new dynamic address to be assigned to the device.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_CHANGE_DA,
            "currentAddress" : currentAddress,
            "newAddress" : newAddress
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cChangeDynamicAddressValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cSetTargetDeviceConfig(self, id: int, targetDeviceConfigEntries: dict) -> dict:
        """
        This method sets the configuration for target devices on the I3C bus.

        Parameters
        ----------
        id : int
            A 2-byte integer representing the request ID. The range allowed is [0, 65535].

        targetDeviceConfigEntries : dict
            A dictionary containing configuration entries for target devices.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """   
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_SET_TARGET_DEVICE_CONFIG,
            "entries" : targetDeviceConfigEntries
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cSetTargetDeviceConfigValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cWrite(self, id: c_uint16, targetAddress: c_uint8, mode: TransferMode, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, registerAddress: list, data: list) -> dict:
        """
        This method is used to request to the Supernova device to perform an I3C write transfer. The
        I3C write transfer starts with a START condition and ends with a STOP condition.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device for writing data.

        mode : TransferMode
            The transfer mode for the write operation.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for the write operation.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for the write operation.

        registerAddress : list
            A list containing the memory/register address of the I3C target internal memory
            from which data will be written. It can hold from 0 to 4 bytes.

        data : list
            A list containing the data to be written to the device.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_TRANSFER,
            "commandType" : I3cCommandType.REGULAR_COMMAND,
            "isReadOrWrite" : TransferDirection.WRITE,
            "targetAddress" : targetAddress,
            "mode" : mode,
            "pushPullRate" : pushPullRate,
            "openDrainRate" : openDrainRate,
            "registerAddress" : registerAddress,
            "data" : data
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cWriteValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cRead(self, id: c_uint16, targetAddress: c_uint8, mode: TransferMode, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate,registerAddress: list, length: c_uint16) -> dict:
        """
        This method is used to request to the USB device to perform an I3C read transfer. The
        I3C read transfer starts with a START condition and ends with a STOP condition.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which data will be read.

        mode : TransferMode
            The transfer mode for the read operation.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for the read operation.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for the read operation.

        registerAddress : list
            A list containing the memory/register address of the I3C target internal memory
            from which data will be read. It can hold from 0 to 4 bytes.

        length : c_uint16
            The length of the data to be read.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_TRANSFER,
            "commandType" : I3cCommandType.REGULAR_COMMAND,
            "isReadOrWrite" : TransferDirection.READ,
            "targetAddress" : targetAddress,
            "mode" : mode,
            "pushPullRate" : pushPullRate,
            "openDrainRate" : openDrainRate,
            "registerAddress" : registerAddress,
            "length" : length
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cReadValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cSendCCC(self, id: c_uint16, cmdType: c_uint8, isReadOrWrite: TransferDirection, targetAddress: c_uint8, mode: TransferMode, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, defByte: c_uint8, ccc: c_uint8, length: c_uint16, data: list) -> dict:
        """
        This method is used to send CCCs on the Supernova, it is wrapped by the specific CCCs to change its functionality.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which data will be read.

        mode : TransferMode
            The transfer mode for the read operation.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for the read operation.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for the read operation.

        registerAddress : list
            A list containing the memory/register address of the I3C target internal memory
            from which data will be read. It can hold from 0 to 4 bytes.

        length : c_uint16
            The length of the data to be read.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_TRANSFER,
            "commandType" : cmdType,
            "isReadOrWrite" : isReadOrWrite,
            "targetAddress" : targetAddress,
            "mode" : mode,
            "pushPullRate" : pushPullRate,
            "openDrainRate" : openDrainRate,
            "defByte" : defByte,
            "ccc" : ccc,
            "length" : length,
            "data" : data
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cSendCccValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cTargetReset(self, id: c_uint16, targetAddress: c_uint8, defByte: c_uint8, isReadOrWrite: TransferDirection, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method resets a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device to be reset.

        defByte : c_uint8
            The definition byte for the reset operation.

        isReadOrWrite : TransferDirection
            Specifies whether the reset operation is a read or write.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for the reset operation.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for the reset operation.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : I3C_TRANSFER,
            "commandType" : I3cCommandType.TARGET_RESET_PATTERN,
            "isReadOrWrite" : isReadOrWrite,
            "targetAddress" : targetAddress,
            "pushPullRate" : pushPullRate,
            "openDrainRate" : openDrainRate,
            "defByte" : defByte
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetResetValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cTargetInit(self, id: c_uint16, memoryLayout: I3cTargetMemoryLayout_t, uSecondsToWaitForIbi: c_uint8 = 1, maxReadLength: c_uint16 = 1024, maxWriteLength: c_uint16 = 1024, targetFeatures: c_uint8 = 12) -> dict:
        '''
        This function is in charge of initializing the I3C peripheral of the Supernova as a target.

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        memoryLayout: I3cTargetMemoryLayout_t
            Layout of the memory that the target represents.

        uSecondsToWaitForIbi: c_uint16
                              sMicro seconds to allow an In-Band Interrupt (IBI) to drive SDA low when the controller is not doing so

        maxReadLength: c_uint16 
                       Maximum read length that the user wants the Supernova to handle.
        
        maxWriteLength: c_uint16
                        Maximum write length that the user wants the Supernova to handle.
        
        targetFeatures: c_uint8
                        Series of flags that describe the features of the Supernova in target mode

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        

        '''
        metadata = {
            "id" : id,
            "command" : I3C_TARGET_INIT,
            "memoryLayout" : memoryLayout,
            "uSecondsToWaitForIbi" : uSecondsToWaitForIbi,
            "maxReadLength" : maxReadLength,
            "maxWriteLength" : maxWriteLength,
            "i3cTargetFeatures" : targetFeatures
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetInitValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cTargetSetBcr(self, id: c_uint16, maxDataSpeedLimit: I3cTargetMaxDataSpeedLimit_t, ibiReqCapable: I3cTargetIbiCapable_t, ibiPayload: I3cTargetIbiPayload_t, offlineCapable: I3cTargetOfflineCap_t, virtTargSupport: I3cTargetVirtSupport_t, deviceRole: I3cTargetDeviceRole_t) -> dict:
        '''
        This function is in charge of modifying the BCR of the Supernova (in I3C target mode) via USB. 
        Note: BCR[5] which indicates advanced capabilities (in this case HDR DDR mode) is a a read-only bit, always set by the peripheral.

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        maxDataSpeedLimit: I3cTargetMaxDataSpeedLimit_t
             Indicates if there is a data speed limit.

        ibiReqCapable: I3cTargetIbiCapable_t
             Shows if the Supernova is capable of requesting IBIs.

        ibiPayload: I3cTargetIbiPayload_t
             Shows if the Supernova is capable of sending data during IBIs.

        offlineCapable: I3cTargetOfflineCap_t
             Specifies wether the Supernova has offline capabilities or not.

        virtTargSupport: I3cTargetVirtSupport_t 
             Indicates if the Supernova supports virtual target mode.

        deviceRole: I3cTargetDeviceRole_t
             Specifies the role.

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        

        '''

        metadata = {
            "id" : id,
            "command" : I3C_TARGET_SET_BCR,
            "maxDataSpeedLimit" : maxDataSpeedLimit, 
            "ibiReqCapable" : ibiReqCapable, 
            "ibiPayload" : ibiPayload, 
            "offlineCapable" : offlineCapable, 
            "virtTargSupport" : virtTargSupport, 
            "deviceRole" : deviceRole
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetSetBcrValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def i3cTargetSetDcr(self, id: c_uint16, dcrValue: I3cTargetDcr_t) -> dict:
        '''
        This function is in charge of modifying the DCR of the Supernova (in I3C target mode) via USB. 

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        dcrValue: I3cTargetDcr_t
             Determines the type of device the target Supernova represents which defines the DCR.

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        

        '''

        metadata = {
            "id" : id,
            "command" : I3C_TARGET_SET_DCR,
            "dcrValue" : dcrValue
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetSetDcrValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def i3cTargetSetPid(self, id: c_uint16, pid: list) -> dict:
        '''
        This function is in charge of modifying the PID of the Supernova (in I3C target mode) via USB. 

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        pid: list
             PID to set by the user, PID[0] is the LSB.

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        

        '''

        metadata = {
            "id" : id,
            "command" : I3C_TARGET_SET_PID,
            "PID" : pid
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetSetPidValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def i3cTargetSetStaticAddr(self, id: c_uint16, staticAddr: c_uint8) -> dict:
        '''
        This function is in charge of modifying the static address of the Supernova (in I3C target mode) via USB. 

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        staticAddr: c_uint8
             Static address to set.

        Returns
        -------
        dict:
            Dictionary that indicates the result of the validation of the data        

        '''

        metadata = {
            "id" : id,
            "command" : I3C_TARGET_SET_STATIC_ADDR,
            "staticAddress" : staticAddr
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetSetStaticAddrValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def i3cTargetSetConfiguration(self, id: c_uint16, uSecondsToWaitForIbi: c_uint8, maxReadLength: c_uint16, maxWriteLength: c_uint16, targetFeatures: c_uint8) -> dict:
        '''
        This function is in charge of modifying the data set by the user for the I3C_TARGET_SET_CONFIGURATION command to be later sent to the Supernova (in I3C target mode) 
        via USB. It calls i3cTargetSetConfValidator which validates the data and serializes it using i3cTargetSetConfSerializer.

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        uSecondsToWaitForIbi: c_uint16
                              sMicro seconds to allow an In-Band Interrupt (IBI) to drive SDA low when the controller is not doing so

        maxReadLength: c_uint16 
                       Maximum read length that the user wants the Supernova to handle.
        
        maxWriteLength: c_uint16
                        Maximum write length that the user wants the Supernova to handle.
        
        targetFeatures: c_uint8
                        Series of flags that describe the features of the Supernova in target mode

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        

        '''

        metadata = {
            "id" : id,
            "command" : I3C_TARGET_SET_CONFIGURATION,
            "uSecondsToWaitForIbi" : uSecondsToWaitForIbi,
            "maxReadLength" : maxReadLength,
            "maxWriteLength" : maxWriteLength,
            "i3cTargetFeatures" : targetFeatures
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetSetConfValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cTargetWriteMemory(self, id: c_uint16, memoryAddr: c_uint16, data: list) -> dict:
        '''
        This function is in charge of modifying the data set by the user for the I3C_TARGET_WRITE_MEMORY command to be later sent to the Supernova (in I3C target mode) 
        via USB. It calls i3cTargetWriteMemValidator which validates the data and serializes it using i3cTargetWriteMemSerializer.

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        memoryAddr: c_uint16  
                    Address of the memory to start to write
                    
        data: list 
              list of bytes that represents the data the user wants to write

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        

        '''

        metadata = {
            "id" : id,
            "command" : I3C_TARGET_WRITE_MEMORY,
            "memoryAddr": memoryAddr,
            "data" : data,
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetWriteMemValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()

    @__checkFunctionalities
    def i3cTargetReadMemory(self, id: c_uint16, memoryAddr: c_uint16, length: c_uint16) -> dict:
        '''
        This function is in charge of modifying the data set by the user for the I3C_TARGET_READ_MEMORY command to be later sent to the Supernova (in I3C target mode) 
        via USB. It calls i3cTargetReadMemValidator which validates the data and serializes it using i3cTargetReadMemSerializer.

        Arguments
        ---------
        id : c_uint16
             An integer number that identifies the transfer.

        memoryAddr: c_uint16  
                    Address of the memory to start to read
                    
        length: c_uint16 
                data length the user intends to read

        Returns
        -------
        dict:
            dictionary that indicates the result of the validation of the data        

        '''

        metadata = {
            "id" : id,
            "command" : I3C_TARGET_READ_MEMORY,
            "memoryAddr": memoryAddr,
            "length": length,
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = i3cTargetReadMemValidator(metadata)

        # If the command was built successfully, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata= request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    # CCC Wrappers ----------------------------------------------------------------------

    def i3cGETBCR(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Bus Characteristics Register (BCR) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the BCR.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the BCR.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the BCR.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETBCR
        DATA_LEN        = 1
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETDCR(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Device Characteristics Register (DCR) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the DCR.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the DCR.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the DCR.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETDCR
        DATA_LEN        = 1
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETPID(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Provisioned ID (PID) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the PID.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the PID.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the PID.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        -------
        Note: The retrieved PID is in MSB order
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETPID
        DATA_LEN        = 6
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETACCCR(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Accept Controller Role (ACCCR) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the ACCCR.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the ACCCR.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the ACCCR.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETACCCR
        DATA_LEN        = 1
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETMXDS(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Maximum Data Speed (MXDS) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the MXDS.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the MXDS.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the MXDS.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETMXDS
        DATA_LEN        = 5
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETMRL(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Maximum Read Length (MRL) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the MRL.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the MRL.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the MRL.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETMRL
        DATA_LEN        = 7                                     # Maximum possible number of bytes returned. See Section 5.1.9.3.6 Set/Get Max Read Length (SETMRL/GETMRL) in MIPI I3C Basic V1.1.1 specifications.
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETMWL(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Maximum Write Length (MWL) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the MWL.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the MWL.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the MWL.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETMWL
        DATA_LEN        = 2
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETXTIME(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Exchange Timing Support Information (XTIME) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the XTIME.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the XTIME.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the XTIME.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETXTIME
        DATA_LEN        = 4
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cGETCAPS(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method gets the Optional Feature Capabilities (CAPS) from a target device on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The address of the target device from which to get the CAPS.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for obtaining the CAPS.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for obtaining the CAPS.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_GETCAPS
        DATA_LEN        = 4
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cRSTDAA(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method resets all Dynamic Address Assignment (DAA) activity on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for resetting DAA activity.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for resetting DAA activity.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_RSTDAA
        DATA_LEN        = 0x00
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastENEC(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, events: list) -> dict:
        """
        This method broadcasts the Enable Target Events Command (ENEC) command on the I3C bus. 

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID.The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting ENEC.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting ENEC.

        events : list
            A list containing events to be enabled.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        CMD_TYPE        = I3cCommandType.CCC_WITH_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_ENEC
        DATA_LEN        = 0x01
        DATA            = []

        # Get target events byte
        eventsByte       = 0
        for event in events:
            eventsByte |= event.value

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, eventsByte, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastDISEC(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, events: list) -> dict:
        """
        This method broadcasts the Disable Target Events Command (DISEC) on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting DISEC.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting DISEC.

        events : list
            A list containing events to be disabled.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        
        CMD_TYPE        = I3cCommandType.CCC_WITH_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_DISEC
        DATA_LEN        = 0x01
        DATA            = []

        # Get target events byte
        eventsByte       = 0
        for event in events:
            eventsByte |= event.value

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, eventsByte, CCC_CODE, DATA_LEN, DATA)

    def i3cDirectENEC(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, events: list) -> dict:
        """
        This method directly Enables Target Events Command (ENEC) to a specific target address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address to which ENEC is sent.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for sending ENEC.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for sending ENEC.

        events : list
            A list containing events to be enabled.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.D_ENEC
        DATA_LEN        = 0x01

        # Get target events byte
        eventsByte       = 0
        for event in events:
            eventsByte |= event.value

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, [eventsByte])

    def i3cDirectDISEC(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, events: list) -> dict:
        """
        This method directly Disables Target Events Command (DISEC) to a specific target address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address to which DISEC is sent.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for sending DISEC.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for sending DISEC.

        events : list
            A list containing events to be disabled.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.D_DISEC
        DATA_LEN        = 0x01

        # Get target events byte
        eventsByte       = 0
        for event in events:
            eventsByte |= event.value

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, [eventsByte])

    def i3cSETDASA(self, id: c_uint16, staticAddress: c_uint8, dynamicAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method Sets Dynamic Address from Static Address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        staticAddress : c_uint8
            The static address to be set.

        dynamicAddress : c_uint8
            The dynamic address to be set.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for setting DA/SA.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for setting DA/SA.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.D_SETDASA
        DATA_LEN        = 0x01
        DATA            = [(dynamicAddress << 1)]
        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, staticAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cSETNEWDA(self, id: c_uint16, oldAddress: c_uint8, newAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method Sets a New Dynamic Address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        oldAddress : c_uint8
            The old dynamic address to be replaced.

        newAddress : c_uint8
            The new dynamic address to be set.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for setting the new dynamic address.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for setting the new dynamic address.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        # TODO: Test. Add change in target device Table.
        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.D_SETNEWDA
        DATA_LEN        = 0x01
        DATA            = [(newAddress << 1)]
        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, oldAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cDirectSETGRPA(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, grpa: c_uint8) -> dict:
        """
        This method sets Group Address on a specific target address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address on which to set GRPA.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for setting GRPA.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for setting GRPA.

        grpa : c_uint8
            The value to be set in the Group Address.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_SETGRPA
        DATA_LEN        = 1                                     # Group Address to set. See Section 5.1.9.3.27
        DATA            = [(grpa << 1)]                         # We need to shift the group address by 1 bit to the left.

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cBroadcastRSTGRPA(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method broadcasts a Reset Group Address command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting RSTGRPA.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting RSTGRPA.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_RSTGRPA
        DATA_LEN        = 0                                     # Group Address to set. See Section 5.1.9.3.28
        DATA            = []
        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)


    def i3cDirectRSTGRPA(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method sends a Resets Group Address command to a specific target address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address to which RSTGRPA is sent.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for sending RSTGRPA.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for sending RSTGRPA.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_RSTGRPA
        DATA_LEN        = 0
        DATA            = []                                    # Group Address to set. See Section 5.1.9.3.27

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)


    def i3cDirectSETMRL(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, mrl: c_uint16) -> dict:
        """
        This method directly sets the Maximum Read Length (MRL) for a specific target address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address for which MRL is set.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for setting MRL.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for setting MRL.

        mrl : c_uint16
            The value to be set as the Maximum Read Length.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_SETMRL
        DATA_LEN        = 2                                     # Maximum possible number of bytes returned. See Section 5.1.9.3.6 Set/Get Max Read Length (SETMRL/GETMRL) in MIPI I3C Basic V1.1.1 specifications.
        DATA            = [(mrl >> 8), (mrl)]

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cDirectSETMWL(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, mwl: c_uint16) -> dict:
        """
        This method directly sets the Maximum Write Length (MWL) for a specific target address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address for which MWL is set.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for setting MWL.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for setting MWL.

        mwl : c_uint16
            The value to be set as the Maximum Write Length.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CMD         = CCC.D_SETMWL
        DATA_LEN        = 2
        DATA            = [(mwl >> 8), (mwl)]

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, targetAddress, MODE, pushPullRate, openDrainRate, 0x00, CCC_CMD, DATA_LEN, DATA)

    def i3cBroadcastSETMWL(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, mwl: c_uint16) -> dict:
        """
        This method broadcasts the setting of Maximum Write Length (MWL) to all devices on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting MWL.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting MWL.

        mwl : c_uint16
            The value to be set as the Maximum Write Length for all devices.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_SETMWL
        DATA_LEN        = 2
        DATA            = [(mwl >> 8), (mwl)]
        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastSETMRL(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, mrl: c_uint16) -> dict:
        """
        This method broadcasts the setting of Maximum Read Length (MRL) to all devices on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting MRL.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting MRL.

        mrl : c_uint16
            The value to be set as the Maximum Read Length for all devices.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_SETMRL
        DATA_LEN        = 2
        DATA            = [(mrl >> 8), (mrl)]
        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cSETAASA(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method set All Addresses to Static Address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for setting all addresses.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for setting all addresses.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_SETAASA
        DATA_LEN        = 0x00
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastENDXFER(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, definingByte:c_uint8, callback,data:list = []) -> dict:
        """
        This method broadcasts the Data Transfer Ending Procedure Control (ENDXFER) command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting ENDXFER.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting ENDXFER.

        definingByte : c_uint8
            The defining byte for the ENDXFER command.

        callback : callback
            The callback function triggered after the ENDXFER command.

        data : list, optional
            Additional data for the ENDXFER command, default is an empty list.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITH_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_ENDXFER

        DATA_LEN        = 0x00
        aux_data = []
        if (data):
            aux_data.extend(data)
        DATA            = aux_data
        DATA_LEN        = len(DATA)

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, definingByte, CCC_CODE, DATA_LEN, DATA)

    def i3cDirectENDXFER(self, id: c_uint16, targetAddress, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, definingByte:c_uint8, data:c_uint8) -> dict:
        """
        This method directly sends the Data Transfer Ending Procedure Control (ENDXFER) command to a specific target address on the I3C bus..

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address to which ENDXFER is sent.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for sending ENDXFER.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for sending ENDXFER.

        definingByte : c_uint8
            The defining byte for the ENDXFER command.

        data : c_uint8
            Additional data for the ENDXFER command.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITH_DEFINING_BYTE
        TARGET_ADDR     = targetAddress
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.D_ENDXFER
        DATA_LEN        = 0x01
        DATA            = [data]

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, definingByte, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastSETXTIME(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, subCMDByte:c_uint8, callback, data:list = []) -> dict:
        """
        This method broadcasts the Set Exchange Timing Information (SETXTIME) command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting SETXTIME.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting SETXTIME.

        subCMDByte : c_uint8
            The sub-command byte for the SETXTIME command.

        callback : callback
            The callback function triggered after the SETXTIME command.

        data : list, optional
            Additional data for the SETXTIME command, default is an empty list.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_SETXTIME
        DATA_LEN        = 0x01
        aux_data = [subCMDByte]
        if (data):
            aux_data.extend(data)
        DATA            = aux_data
        DATA_LEN        = len(DATA)

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cDirectSETXTIME(self, id: c_uint16, targetAddress, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, subCMDByte:c_uint8, callback, data:list = []) -> dict:
        """
        This method directly sends the Set Exchange Timing Information (SETXTIME) command to a specific target address on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        targetAddress : c_uint8
            The specific target address to which SETXTIME is sent.

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for sending SETXTIME.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for sending SETXTIME.

        subCMDByte : c_uint8
            The sub-command byte for the SETXTIME command.

        callback : callback
            The callback function triggered after the SETXTIME command.

        data : list, optional
            Additional data for the SETXTIME command, default is an empty list.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = targetAddress
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.D_SETXTIME

        aux_data = [subCMDByte]
        if (data):
            aux_data.extend(data)
        DATA            = aux_data
        DATA_LEN        = len(DATA)

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastSETBUSCON(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, context:c_uint8, data:list = []) -> dict:
        """
        This method broadcasts the Set Bus Context (SETBUSCON) command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting SETBUSCON.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting SETBUSCON.

        context : c_uint8
            The context value for the SETBUSCON command.

        data : list, optional
            Additional data for the SETBUSCON command, default is an empty list.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITH_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_SETBUSCON
        DATA_LEN        = 0x01

        aux_data = [context]
        if (data):
            aux_data.extend(data)
        DATA            = aux_data
        DATA_LEN        = len(DATA)

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastENTAS0(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method broadcasts the Enter Activity State 0 (ENTAS0) command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting ENTAS0.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting ENTAS0.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_ENTAS0
        DATA_LEN        = 0x00
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastENTAS1(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method broadcasts the Enter Activity State 1 (ENTAS1) command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting ENTAS1.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting ENTAS1.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_ENTAS1
        DATA_LEN        = 0x00
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastENTAS2(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method broadcasts the Enter Activity State 2 (ENTAS2) command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting ENTAS2.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting ENTAS2.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """
        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_ENTAS2
        DATA_LEN        = 0x00
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cBroadcastENTAS3(self, id: c_uint16, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method broadcasts the Enter Activity State 3 (ENTAS3) command on the I3C bus.

        Parameters
        ----------
        id : c_uint16
            A 2-byte unsigned integer representing the transfer ID. The range allowed is [0, 65535].

        pushPullRate : I3cPushPullTransferRate
            The push-pull rate for broadcasting ENTAS3.

        openDrainRate : I3cOpenDrainTransferRate
            The open-drain rate for broadcasting ENTAS3.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.
        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = 0x7E
        DIRECTION       = TransferDirection.WRITE
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.B_ENTAS3
        DATA_LEN        = 0x00
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    def i3cGETSTATUS(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        """
        This method is used to request to the Supernova controller to perform a GET_STATUS CCC. 

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The allowed range is [0, 65535].

        targetAddress : int
            It is a 1-byte integer that indicates the dynamic address of the target this command is directed to. 

        pushPullTransferRate : I3cPushPullTransferRate
            Push Pull frequency for the transfer.

        openDrainRate : I3cOpenDrainTransferRate
            Open Drain frequency for the transfer.
            
        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """

        CMD_TYPE        = I3cCommandType.CCC_WITHOUT_DEFINING_BYTE
        TARGET_ADDR     = targetAddress
        DIRECTION       = TransferDirection.READ
        MODE            = TransferMode.I3C_SDR
        CCC_CODE        = CCC.D_GETSTATUS
        DATA_LEN        = 2
        DATA            = []

        # API I3C Send CCC
        return self.i3cSendCCC(id, CMD_TYPE, DIRECTION, TARGET_ADDR, MODE, pushPullRate, openDrainRate, 0x00, CCC_CODE, DATA_LEN, DATA)

    # --------------------------UART management --------------------------------------------------------------------

    # ------------------------------UART INIT ----------------------------#
    @__checkFunctionalities
    def uartControllerInit(self, id: int, baudrate: UartControllerBaudRate, hardwareHandshake:bool, parityMode:UartControllerParity, dataSize:UartControllerDataSize, stopBit: UartControllerStopBit ) -> dict:
        """
        This method initializes the UART peripherals

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represents the transfer id. The range allowed is [0, 65535].

        baudrate : UartControllerBaudRate
            This parameter represents the UART TX and RX frequency from the options provided by the UartControllerBaudRate enum.
            The frequency goes from 600bps to up to 115200bps.

        hardwareHandshake : bool 
            This parameter represents a boolean flag to enable or disable this option.
        
        parityMode: UartControllerParity
            This parameter represents the different parity modes available in the UartControllerParity enum.
            The parity modes are: none, even or odd.
        
        dataSize: UartControllerDataSize
            This parameter represents the different data sizes available in the UartControllerDataSize enum.
            The data sizes are either 7 or 8.

        stopBit: UartControllerStopBit
            This parameter represent the different stop bit configuration available in the UartControllerStopBit enum.
            The stop bit can be of size 1 or 2.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """

        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : UART_CONTROLLER_INIT,
            "baudRate": baudrate,
            "hardwareHandshake": hardwareHandshake,
            "parityMode": parityMode,
            "dataSize": dataSize,
            "stopBitType" : stopBit,
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = uartControllerInitValidator(metadata)

        # If the command was built successfuly, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata = request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    # ------------------------------UART SET ----------------------------#
    @__checkFunctionalities
    def uartControllerSetParameters(self, id: int, baudrate: UartControllerBaudRate, hardwareHandshake:bool, parityMode:UartControllerParity, dataSize:UartControllerDataSize, stopBit: UartControllerStopBit ) -> dict:
        """
        This method sets the UART peripheral parameters

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The range allowed is [0, 65535].

        baudrate : UartControllerBaudRate
            This parameter represents the UART TX and RX frequency from the options provided by the UartControllerBaudRate enum.
            The frequency goes from 600bps to up to 115200bps.

        hardwareHandshake : bool 
            This parameter represents a boolean flag to enable or disable this option.

        parityMode: UartControllerParity
            This parameter represents the different parity modes available in the UartControllerParity enum.
            The parity modes are: none, even or odd.
        
        dataSize: UartControllerDataSize
            This parameter represents the different data sizes available in the UartControllerDataSize enum.
            The data sizes are either 7 or 8.

        stopBit: UartControllerStopBit
            This parameter represent the different stop bit configuration available in the UartControllerStopBit enum.
            The stop bit can be of size 1 or 2.


        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """

        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : UART_CONTROLLER_SET_PARAMETERS,
            "baudRate": baudrate,
            "hardwareHandshake": hardwareHandshake,
            "parityMode": parityMode,
            "dataSize": dataSize,
            "stopBitType" : stopBit,
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = uartControllerSetParametersValidator(metadata)

        # If the command was built successfuly, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata = request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    # ------------------------------UART SEND ----------------------------#
    @__checkFunctionalities
    def uartControllerSendMessage(self, id: int, data: list) -> dict:
        """
        This method is used to request to the the Supernova device to perform an UART send transfer.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represent the transfer id. The range allowed is [0, 65535].

        data : list
            Python list that contains the data transferred in the UART Send. The list holds
            bytes elements, and the maximum length is 1024 bytes.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : UART_CONTROLLER_SEND,
            "data" : data
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = uartControllerSendMessageValidator(metadata)

        # If the command was built successfuly, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata = request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    # SPI management --------------------------------------------------------------------

    @__checkFunctionalities               
    def spiControllerInit(self,
                          id: int,
                          bitOrder: SpiControllerBitOrder,
                          mode: SpiControllerMode,
                          dataWidth: SpiControllerDataWidth,
                          chipSelect: SpiControllerChipSelect,
                          chipSelectPol: SpiControllerChipSelectPolarity,
                          frequency: c_uint32) -> dict:
        """
        This method initializes the SPI peripheral with the specified configuration.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represents the transfer id. The range allowed is [0, 65535].

        bitOrder : SpiControllerBitOrder
            Sets the bit order of the SPI peripheral, could be SpiControllerBitOrder.MSB or SpiControllerBitOrder.LSB.
        
        mode : SpiControllerMode
            Sets the SPI mode, could be SpiControllerMode.MODE_0, SpiControllerMode.MODE_1, SpiControllerMode.MODE_2 or SpiControllerMode.MODE_3.
        
        dataWidth : SpiControllerDataWidth
            Sets the SPI data width, could be SpiControllerDataWidth._8_BITS_DATA, SpiControllerDataWidth._16_BITS_DATA.

        chipSelect : SpiControllerChipSelect
            Sets the SPI chip select, could be SpiControllerChipSelect.CHIP_SELECT_0, SpiControllerChipSelect.CHIP_SELECT_1,
            SpiControllerChipSelect.CHIP_SELECT_2 or SpiControllerChipSelect.CHIP_SELECT_3.

        chipSelectPol : SpiControllerChipSelectPolarity
            Sets the SPI chip select polarity, could be SpiControllerChipSelectPolarity.ACTIVE_LOW or SpiControllerChipSelectPolarity.ACTIVE_HIGH.
        
        frequency : c_uint32
            Sets the SPI Clock frequency in Hz. Currently, the minimum allowed value is 10000 Hz and the maximum allowed value is 50000000 Hz.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : SPI_CONTROLLER_INIT,
            "bitOrder" : bitOrder,
            "mode" : mode,
            "dataWidth" : dataWidth,
            "chipSelect" : chipSelect,
            "chipSelectPol" : chipSelectPol,
            "frequency" : frequency
        }
        
        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = spiControllerInitValidator(metadata)
        
        # If the command was built successfuly, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata = request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def spiControllerSetParameters(self,
                                   id: int,
                                   bitOrder: SpiControllerBitOrder,
                                   mode: SpiControllerMode,
                                   dataWidth: SpiControllerDataWidth,
                                   chipSelect: SpiControllerChipSelect,
                                   chipSelectPol: SpiControllerChipSelectPolarity,
                                   frequency: c_uint32) -> dict:
        """
        This method sets the SPI peripheral configuration.

        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represents the transfer id. The range allowed is [0, 65535].

        bitOrder : SpiControllerBitOrder
            Sets the bit order of the SPI peripheral, could be SpiControllerBitOrder.MSB or SpiControllerBitOrder.LSB.
        
        mode : SpiControllerMode
            Sets the SPI mode, could be SpiControllerMode.MODE_0, SpiControllerMode.MODE_1, SpiControllerMode.MODE_2 or SpiControllerMode.MODE_3.
        
        dataWidth : SpiControllerDataWidth
            Sets the SPI data width, could be SpiControllerDataWidth._8_BITS_DATA, SpiControllerDataWidth._16_BITS_DATA.

        chipSelect : SpiControllerChipSelect
            Sets the SPI chip select, could be SpiControllerChipSelect.CHIP_SELECT_0, SpiControllerChipSelect.CHIP_SELECT_1,
            SpiControllerChipSelect.CHIP_SELECT_2 or SpiControllerChipSelect.CHIP_SELECT_3.

        chipSelectPol : SpiControllerChipSelectPolarity
            Sets the SPI chip select polarity, could be SpiControllerChipSelectPolarity.ACTIVE_LOW or SpiControllerChipSelectPolarity.ACTIVE_HIGH.
        
        frequency : c_uint32
            Sets the SPI Clock frequency in Hz. Currently, the minimum allowed value is 10000 Hz and the maximum allowed value is 50000000 Hz.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : SPI_CONTROLLER_SET_PARAMETERS,
            "bitOrder" : bitOrder,
            "mode" : mode,
            "dataWidth" : dataWidth,
            "chipSelect" : chipSelect,
            "chipSelectPol" : chipSelectPol,
            "frequency" : frequency
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = spiControllerSetParametersValidator(metadata)

        # If the command was built successfuly, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata = request_metadata)

        # Return result in dict format.
        return result.toDictionary()
    
    @__checkFunctionalities
    def spiControllerTransfer(self, id: int, transferLength: int, payload: list) -> dict:
        """
        This method performs a SPI transfer.
        
        Parameters
        ----------
        id : int
            It is a 2-bytes integer that represents the transfer id. The range allowed is [0, 65535].

        transferLength : int
            It is a 2-bytes integer that represents the transfer length. The range allowed is [1, 1024].

        payload : list  
            Python list that contains the SPI data transferred. The list holds bytes elements and
            the maximum length is 1024 bytes.

        Returns
        -------
        dict
            Return the SystemMessage in dictionary format.

        """
        
        # Create Python dict that contains the command required data.
        metadata = {
            "id" : id,
            "command" : SPI_CONTROLLER_TRANSFER,
            "transferLength" : transferLength,
            "payload" : payload
        }

        # Check data validation and serialize the USB request.
        requestsCommand, responseInstance, result = spiControllerTransferValidator(metadata)

        # If the command was built successfuly, send it to the device
        if result.opcode == RequestValidatorOpcode.SUCCESS:

            # Generate a pair request-response
            request_metadata = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(request_metadata = request_metadata)

        # Return result in dict format.
        return result.toDictionary()
