"""
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.ipc.broker import Broker
from .services.ipc.message import Message
from .services import service
from .services.service import IpcMsgOpcode
from .services.service.dispatcher import Dispatcher
from .services.service.usbReceiver import UsbReceiver
from .services.service.usbTransmitter import UsbTransmitter
from .services.service.uartReceiver import UartReceiver
from .services.service.logger import Logger

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

# Import other modules
from .utils.system_message import SystemMessage, SystemMessageOpcode
import inspect

class Supernova:
    """
    This class represents the Binho Nova 2.0 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.
    serialPort : Serial
        This attribute represent the serial port to communicate with the USB device over UART.
    broker : Broker
        This attribute represents the engine that controls the Inter-process communication architecture.
    dispatcher : Dispatcher
        Service dedicated to receive the USB request from the USB Host and delegates them to the corresponding middleware.
    usbRx : UsbReceiver
        Service dedicated to receives the messages from the USB device, using the usbHidDevice attribute.
    usbTx : UsbTransmitter
        Service dedicated to transmit messages to the USB device, using the usbHidDevice attribute.
    logger : Logger
        Service dedicated to write log messages in log files.
    uartReceiver : UartReceiver
        Service dedicated to receive messages from the USB device sent over UART.
    """

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

    def __sendMessage(self, origin, destination, opcode, payload) -> None:
        """
        This method is used to send a new message. The method creates a message and put it into the
        processMessagesQueue.
        """
        message = Message(origin, destination, opcode, payload)
        self.broker.processMessagesQueue.put(message)

    # Public methods ----------------------------------------------------------------------------------

    def open(self, vid=NXP_VID, pid=LPC5536_PID, serial = None, path:str = None, activateLogger=False) -> 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.

        logEnable : bool
            Boolean flag to activate or deactivate the register of log messages.

        Returns
        -------
        dict
            System message converted to a dict format that indicates if the communication with the Supernova device was established
            successfully.
        """

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

        # Connect to a Supernova device handling exceptions.
        try:

            # 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)

            # Initialize Inter-process Communication engine.
            self.broker = Broker()

            # Request delegator
            self.dispatcher = Dispatcher(activateLogger=activateLogger)
            self.broker.registerProcessMember(service.DISPATCHER, self.dispatcher)

            # USB HID Rx thread.
            self.usbRx = UsbReceiver(self.usbHidDevice, 64, activateLogger=activateLogger)
            self.broker.registerProcessMember(service.USB_RECEIVER, self.usbRx)

            # USB HID Tx thread.
            self.usbTx = UsbTransmitter(self.usbHidDevice, 0, activateLogger=activateLogger)
            self.broker.registerProcessMember(service.USB_TRANSMITTER, self.usbTx)

            # Logger thread
            self.logger = Logger(activate=activateLogger)
            self.broker.registerProcessMember(service.LOGGER, self.logger)

            # Start Inter-process Communication.
            self.broker.start()

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

        return sys_response.toDictionary()

    def close(self) -> None:
        """
        This method closes the communication with the Supernova and releases the used memory.
        """
        # Create and send broadcast message.
        sys_message = SystemMessage(SystemMessageOpcode.CLOSE_CONNECTION_REQUEST, None)
        self.__sendMessage(service.USB_HOST, service.BROADCAST, opcode=IpcMsgOpcode.SYSTEM_MESSAGE, payload=sys_message)

        # Block until the device is reset and all threads are killed.
        while self.broker.thread.is_alive():
            pass

        # Close HID device.
        self.usbHidDevice.close()

    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:

        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
            System message converted to a dict format that indicates if the register of the callback was successfully or if the
            callback function doesn't have the correct signature.
        """

        sys_response = SystemMessage(SystemMessageOpcode.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.opcode = SystemMessageOpcode.INVALID_CALLBACK_SIGNATURE
            sys_response.message = "The function must accept 2 arguments: callback(supernova_response, system_message)."

        self.dispatcher.setOnEventCallback(callback_func)

        return sys_response.toDictionary()

    def activateLogger(self, activate: bool = True) -> None:
        """
        This method activates or deactivates the logger. It can be used to activate or deactivate
        the logger after it was created.

        Parameters
        ----------
        activate : bool
            Boolean flag that indicates if the logger is activated or not. If it is activated,
            then all the services will put log messages into the process messages queue and the
            messages received by the Logger in its inputQueue will be appended in the log file.

        Returns
        -------
        None

        """
        self.logger.activate(activate=activate)
        self.dispatcher.activateLogger(activate=activate)
        self.usbRx.activateLogger(activate=activate)
        self.usbTx.activateLogger(activate=activate)

    def enableUartLogger(self, port, baudrate=115200, logEnable=True) -> dict:
        """
        This function allows to receive log messages sent by the device over UART and append
        them in the log files.

        Parameters
        ----------
        port : str
            Serial port name. For instance, 'COMx' string in Windows.

        Returns
        -------
        dict
            System message converted to a dict format that indicates if the serial port was
            opened successfully or an error happened.
        """

        sys_response = SystemMessage(SystemMessageOpcode.OK, f"Serial port {port} opened successfully.")

        try:
            self.serialPort = Serial(port=port, baudrate=baudrate)
            self.serialPort.set_buffer_size(rx_size=128, tx_size=128)

        except SerialException:
            sys_response.message = "Device can not be found or can not be configured."
            self.serialPort = None
        except ValueError:
            sys_response.message = "Parameter out of range."
            self.serialPort = None
        except Exception as exc:
            sys_response.message = f"Unhandled exception {type(exc)} raised. Message: {exc}"
            self.serialPort = None

        # Uart Rx thread.
        if self.serialPort != None:
            self.uartReceiver = UartReceiver(self.serialPort, logEnable=logEnable)
            self.uartReceiver.start()
            self.broker.registerProcessMember(service.UART_RECEIVER, self.uartReceiver)

        return sys_response.toDictionary()

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

    # Get USB strings -------------------------------------------------------------------

    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 RequestValidatorResult 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.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    # Set I3C Bus Voltage strings -------------------------------------------------------------------

    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.
        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
        """

        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def resetDevice(self, id: int) -> dict:
        """
        This function sends a RESET DEVICE command.
        """
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

        # Block until the device is reset and all threads are killed.
        while self.broker.thread.is_alive():
            pass

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

    def enterBootMode(self, id: int) -> dict:
        """
        This function sends a ENTER BOOT MODE command.
        """
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

        # Block until the device is reset and all threads are killed.
        while self.broker.thread.is_alive():
            pass

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

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

    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 RequestValidatorResult 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    # I2C management --------------------------------------------------------------------

    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 RequestValidatorResult 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    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 RequestValidatorResult 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    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 RequestValidatorResult 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    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 RequestValidatorResult 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    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 RequestValidatorResult 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

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

    def i3cInitBus(self, id: int, targetDeviceTable: dict = None) -> dict:

        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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cGetTargetDeviceTable(self, id: int) -> dict:

        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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cGetCapability(self, id: int) -> dict:

        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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cClearFeature(self, id: int, selector: I3cClearFeatureSelector, targetAddress: c_uint8) -> dict:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cSetFeature(self, id: int, selector: I3cSetFeatureSelector, targetAddress: c_uint8) -> dict:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cChangeDynamicAddress(self, id: int, currentAddress: c_uint8, newAddress: c_uint8) -> dict:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cSetTargetDeviceConfig(self, id: int, targetDeviceConfigEntries: dict) -> dict:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cWrite(self, id: c_uint16, targetAddress: c_uint8, mode: TransferMode, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate, registerAddress: list, data: list) -> dict:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cRead(self, id: c_uint16, targetAddress: c_uint8, mode: TransferMode, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate,registerAddress: list, length: c_uint16) -> dict:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    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:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    def i3cTargetReset(self, id: c_uint16, targetAddress: c_uint8, defByte: c_uint8, isReadOrWrite: TransferDirection, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:
        # 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 successfuly, send it to the device
        if result.code == RequestValidatorResultCode.SUCCESS:

            # Generate a pair request-response and send it to the dispacher.
            request = {
                "id": id,
                "requests": requestsCommand,
                "response": responseInstance
            }

            self.__sendMessage(service.USB_HOST, service.DISPATCHER, opcode=IpcMsgOpcode.REQUEST_FROM_HOST_TO_DEVICE, payload=request)

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

    # CCC Wrappers ----------------------------------------------------------------------

    def i3cGETBCR(self, id: c_uint16, targetAddress: c_uint8, pushPullRate: I3cPushPullTransferRate, openDrainRate: I3cOpenDrainTransferRate) -> dict:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:
        # 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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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:

        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)