2016-02-08 03:03 AM
How to make on STM32F4 USB a host with support of the Composite device?
I made the project in CubeMX, but in this project there is a support of different classes of devices, but there is no support of the Composite device. #host #usb2016-02-09 02:56 AM
Hi g.marat,
Could you specify which version of CubeMx you are using ? -Hannibal-2016-02-09 04:18 AM
Version 4.12.0
2016-03-10 09:57 AM
Hi g.marat,
Try the new version 4.13.0-Hannibal-2016-03-11 05:23 AM
Regardless of CubeMX version,
STM32_USB_Host_Library V3.2.2 / 07-July-2015 (included in the latest STM32Cube_FW_F4_V1.11.0) doesn't support composite device yet. It is obvious when you see this code in usbh_core.c USBH_StatusTypeDef USBH_Process(USBH_HandleTypeDef *phost) { ... switch (phost->gState) { ... case HOST_CHECK_CLASS: if(phost->ClassNumber == 0) { USBH_UsrLog (''No Class has been registered.''); } else { phost->pActiveClass = NULL; for (idx = 0; idx < USBH_MAX_NUM_SUPPORTED_CLASS ; idx ++) { if(phost->pClass[idx]->ClassCode == phost->device.CfgDesc.Itf_Desc[0].bInterfaceClass) // <---- { phost->pActiveClass = phost->pClass[idx]; } } Just the first interface (Itf_Desc[0]) is hard-coded, here. To support a composite device, ''case HOST_CHECK_CLASS:'',''case HOST_CLASS_REQUEST:'', and ''case HOST_CLASS:'' should be modified so that these cases do their job for each interface. Tsuneo2017-01-19 12:12 AM
Hi, i'm trying to do the similar thing to g.marat.
I used stm32f4 discovery board to communicate with Dualshock4 (PS4's controller) which has 4 interface (bNumInterfaces) but there are 6
element
s in the Itf_Desc[] array.After i connected then read the CfgDesc i found that only Itf_Desc[5] is HID class. So now i'm trying to communicate with that interface.
Here is what i had done:
in case HOST_CHECK_CLASS: hard-coded Itf_Desc[5]
in function: USBH_HID_InterfaceInit, i set phost->Control.pipe_in=HID_Handle->InPipe and phost->Control.pipe_out = HID_Handle->OutPipe .
but then i got stuck at USBH_HID_GetHIDDescriptor because USBH_HandleEnum 's state is USBH_BUSY and URB_Status is USBH_URB_NOTREADY.
hope you could help my case.
2017-01-19 09:06 AM
Liêm Lê wrote:
Dualshock4 (PS4's controller) which has 4 interface (bNumInterfaces) but there are 6
element
s in the Itf_Desc[] array.It sounds odd.
I don't have Dualshock 4. I've searched for its descriptors on the net, found them on these sites.http://www.psdevwiki.com/ps4/DS4-USB
http://eleccelerator.com/wiki/index.php?title=DualShock_4
According to these sites, Dualshock 4 exposes just single HID interface.A couple of more sites also show the same descriptors.Once on your PC, read out the descriptors of your Dualshock4.
You may have a variant of Dualshock4 ;)Here are utilities for each OS,
Windows - USBViewhttp://www.ftdichip.com/Support/Utilities.htm ♯ MicrosoftUSBView
Linux - lsusb commandMacOSX - USBProber If your Dualshock4 would be the standard one (single HID I/F), you may start with HID host example, generated by CubeMX.Tsuneo
2017-01-19 09:29 PM
First of all, thanks you for your time
yep , i had already read those site few days ago but their information is for old Dualshock 4 (CUH-ZCT1) , mine is CUH-ZCT2 and i cant find any site show this version's descriptors.
the PID of CUH-ZCT2 is 0x09CC when CUH-ZCT1 is 0x05C4
I had successfully read my keyboard and mouse with HID host generated by CubeMX.
I have just tested with my friend's Dualshock 3 which have 1 interface, and it had gone further
Here is the descriptor i get when use USBView :
Device Descriptor:
bcdUSB: 0x0200bDeviceClass: 0x00bDeviceSubClass: 0x00bDeviceProtocol: 0x00bMaxPacketSize0: 0x40 (64)idVendor: 0x054C (Sony Corporation)idProduct: 0x09CCbcdDevice: 0x0100iManufacturer: 0x010x0409: 'Sony Interactive Entertainment'iProduct: 0x020x0409: 'Wireless Controller'iSerialNumber: 0x00bNumConfigurations: 0x01ConnectionStatus: DeviceConnected
Current Config Value: 0x01Device Bus Speed: FullDevice Address: 0x0AOpen Pipes: 2Endpoint Descriptor:
bEndpointAddress: 0x84 INTransfer Type: InterruptwMaxPacketSize: 0x0040 (64)bInterval: 0x05Endpoint Descriptor:
bEndpointAddress: 0x03 OUTTransfer Type: InterruptwMaxPacketSize: 0x0040 (64)bInterval: 0x05Configuration Descriptor:
wTotalLength: 0x00E1bNumInterfaces: 0x04bConfigurationValue: 0x01iConfiguration: 0x00bmAttributes: 0xC0 (Bus Powered Self Powered )MaxPower: 0xFA (500 Ma)Interface Descriptor:
bInterfaceNumber: 0x00bAlternateSetting: 0x00bNumEndpoints: 0x00bInterfaceClass: 0x01 (Audio)bInterfaceSubClass: 0x01 (Audio Control)bInterfaceProtocol: 0x00iInterface: 0x00Audio Control Interface Header Descriptor:
bLength: 0x0AbDescriptorType: 0x24bDescriptorSubtype: 0x01bcdADC: 0x0100wTotalLength: 0x0047bInCollection: 0x02baInterfaceNr[1]: 0x01baInterfaceNr[2]: 0x02Audio Control Input Terminal Descriptor:
bLength: 0x0CbDescriptorType: 0x24bDescriptorSubtype: 0x02bTerminalID: 0x01wTerminalType: 0x0101 (USB streaming)bAssocTerminal: 0x06bNrChannels: 0x02wChannelConfig: 0x0003iChannelNames: 0x00iTerminal: 0x00Audio Control Feature Unit Descriptor:
bLength: 0x0AbDescriptorType: 0x24bDescriptorSubtype: 0x06bUnitID: 0x02bSourceID: 0x01bControlSize: 0x01bmaControls[0]:03 bmaControls[1]:00 bmaControls[2]:00 iFeature: 0x00Audio Control Output Terminal Descriptor:
bLength: 0x09bDescriptorType: 0x24bDescriptorSubtype: 0x03bTerminalID: 0x03wTerminalType: 0x0402 (Headset)bAssocTerminal: 0x04bSoruceID: 0x02iTerminal: 0x00Audio Control Input Terminal Descriptor:
bLength: 0x0CbDescriptorType: 0x24bDescriptorSubtype: 0x02bTerminalID: 0x04wTerminalType: 0x0402 (Headset)bAssocTerminal: 0x03bNrChannels: 0x01wChannelConfig: 0x0000iChannelNames: 0x00iTerminal: 0x00Audio Control Feature Unit Descriptor:
bLength: 0x09bDescriptorType: 0x24bDescriptorSubtype: 0x06bUnitID: 0x05bSourceID: 0x04bControlSize: 0x01bmaControls[0]:03 bmaControls[1]:00 iFeature: 0x00Audio Control Output Terminal Descriptor:
bLength: 0x09bDescriptorType: 0x24bDescriptorSubtype: 0x03bTerminalID: 0x06wTerminalType: 0x0101 (USB streaming)bAssocTerminal: 0x01bSoruceID: 0x05iTerminal: 0x00Interface Descriptor:
bInterfaceNumber: 0x01bAlternateSetting: 0x00bNumEndpoints: 0x00bInterfaceClass: 0x01 (Audio)bInterfaceSubClass: 0x02 (Audio Streaming)bInterfaceProtocol: 0x00iInterface: 0x00Interface Descriptor:
bInterfaceNumber: 0x01bAlternateSetting: 0x01bNumEndpoints: 0x01bInterfaceClass: 0x01 (Audio)bInterfaceSubClass: 0x02 (Audio Streaming)bInterfaceProtocol: 0x00iInterface: 0x00Audio Streaming Class Specific Interface Descriptor:
bLength: 0x07bDescriptorType: 0x24bDescriptorSubtype: 0x01bTerminalLink: 0x01bDelay: 0x01wFormatTag: 0x0001 (PCM)Audio Streaming Format Type Descriptor:
bLength: 0x0BbDescriptorType: 0x24bDescriptorSubtype: 0x02bFormatType: 0x01bNrChannels: 0x02bSubframeSize: 0x02bBitResolution: 0x10bSamFreqType: 0x01tSamFreq[1]: 0x007D00 (32000 Hz)Endpoint Descriptor:
bEndpointAddress: 0x01 OUTTransfer Type: IsochronouswMaxPacketSize: 0x0084 (132)wInterval: 0x0001bSyncAddress: 0x00Audio Streaming Class Specific Audio Data Endpoint Descriptor:
bLength: 0x07bDescriptorType: 0x25bDescriptorSubtype: 0x01bmAttributes: 0x00bLockDelayUnits: 0x00wLockDelay: 0x0000Interface Descriptor:
bInterfaceNumber: 0x02bAlternateSetting: 0x00bNumEndpoints: 0x00bInterfaceClass: 0x01 (Audio)bInterfaceSubClass: 0x02 (Audio Streaming)bInterfaceProtocol: 0x00iInterface: 0x00Interface Descriptor:
bInterfaceNumber: 0x02bAlternateSetting: 0x01bNumEndpoints: 0x01bInterfaceClass: 0x01 (Audio)bInterfaceSubClass: 0x02 (Audio Streaming)bInterfaceProtocol: 0x00iInterface: 0x00Audio Streaming Class Specific Interface Descriptor:
bLength: 0x07bDescriptorType: 0x24bDescriptorSubtype: 0x01bTerminalLink: 0x06bDelay: 0x01wFormatTag: 0x0001 (PCM)Audio Streaming Format Type Descriptor:
bLength: 0x0BbDescriptorType: 0x24bDescriptorSubtype: 0x02bFormatType: 0x01bNrChannels: 0x01bSubframeSize: 0x02bBitResolution: 0x10bSamFreqType: 0x01tSamFreq[1]: 0x003E80 (16000 Hz)Endpoint Descriptor:
bEndpointAddress: 0x82 INTransfer Type: IsochronouswMaxPacketSize: 0x0022 (34)wInterval: 0x0001bSyncAddress: 0x00Audio Streaming Class Specific Audio Data Endpoint Descriptor:
bLength: 0x07bDescriptorType: 0x25bDescriptorSubtype: 0x01bmAttributes: 0x00bLockDelayUnits: 0x00wLockDelay: 0x0000Interface Descriptor:
bInterfaceNumber: 0x03bAlternateSetting: 0x00bNumEndpoints: 0x02bInterfaceClass: 0x03 (HID)bInterfaceSubClass: 0x00bInterfaceProtocol: 0x00iInterface: 0x00HID Descriptor:
bcdHID: 0x0111bCountryCode: 0x00bNumDescriptors: 0x01bDescriptorType: 0x22wDescriptorLength: 0x01FBEndpoint Descriptor:
bEndpointAddress: 0x84 INTransfer Type: InterruptwMaxPacketSize: 0x0040 (64)bInterval: 0x05Endpoint Descriptor:
bEndpointAddress: 0x03 OUTTransfer Type: InterruptwMaxPacketSize: 0x0040 (64)bInterval: 0x05And it's similar which the one i got by stm32f4.
2017-01-20 06:36 AM
Liêm Lê wrote:
mine is CUH-ZCT2 (Dualshock 4 ver2)
OK, your USB device is a composite device of four interfaces,
0 Audio Control 1 Audio Streaming (OUT) 2 Audio Streaming (IN) 3 HIDYou may still start at a USBHID host generated by CubeMX.
CubeMX: v4.0 CubeF4: v1.0The Cube HID host is customized for boot mouse and keyboard.
A little modification will release this limitation. Here is the snippet.As your device has four interfaces, this macro should be touched to accept more interfaces than the default.
usbh_conf.h
// line 56:
#define USBH_MAX_NUM_INTERFACES 2 <--- 4
�?�?�?
To accept the HID interface of DS4, USBH_HID_InterfaceInit is modified.
usbh_hid.c
static USBH_StatusTypeDef USBH_HID_InterfaceInit (USBH_HandleTypeDef *phost)
{
...
// replace line 147
// interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode, HID_BOOT_CODE, 0xFF);
interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode, 0x00, 0x00);
...
// delete from line 161 to 176
/*Decode Bootclass Protocol: Mouse or Keyboard*/
if(phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bInterfaceProtocol == HID_KEYBRD_BOOT_CODE)
{
USBH_UsrLog ('KeyBoard device found!');
HID_Handle->Init = USBH_HID_KeybdInit;
}
else if(phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bInterfaceProtocol == HID_MOUSE_BOOT_CODE)
{
USBH_UsrLog ('Mouse device found!');
HID_Handle->Init = USBH_HID_MouseInit;
}
else
{
USBH_UsrLog ('Protocol not supported.');
return USBH_FAIL;
}
// add this line, instead
HID_Handle->Init = USBH_HID_MouseInit; // replace with USBH_HID_DS4Init, after DS4 handler is established
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
Now that, the modified host code catches the HID interface of the DS4.
As the next steps,
1) Read out the report descriptor from your DS4 2) Identify report fields for DS4 axes, buttons, hat switches and specific functionsThe report descriptor defines the size of reports and their fields for input/output/feature reports (data block). It also gives suggestions of field type (Axis, button, hat switch). But just with the report descriptor, each data field can't be identified. You'll identify each field by finding the field whose value changes when you would manipulate each axis/botton.
There are a couple way to read out the report descriptor, here is easier one ;)
0. Download the trial (33 days) of USBlyzer
http://www.usblyzer.com/download.htm
1. Run USBlyzer. Under Capture menu, confirm that 'Capture Hot-plugged' has check mark. If not, select it once. 2. push 'Start Capture' button 3. Plug in your DS4 to the PC. - USBlyzer starts to log the traffic 4. After 3-4 seconds, push 'Stop Capture' button 5. On the Device Tree pane, find the target and click 'USB Human Interface Device' just above the target. - USB Properties pane shows the descriptors of the mouse with HID report descriptor. 6. Right click on USB Properties pane, and choose 'Export' on the right menu. - file dialog saves a HTML file of descriptors.Please post the descriptors HTML file, here.
We'll analyze the report descriptor, together.Tsuneo
2017-01-21 05:27 AM
Hi Tsuneo,
As i mentioned before even the cfgDesc said it has 4 interface (bNumInterfaces) but there is 6 elements Itf_Desc[] array. so the USBH_MAX_NUM_INTERFACES should be 6.(i attached an image).
And i had done the same thing you said about
USBH_HID_InterfaceInit() function. Not just that, the USBH_SelectInterface (phost, interface) need to be modified too.
USBH_StatusTypeDef USBH_SelectInterface(USBH_HandleTypeDef *phost, uint8_t interface) { USBH_StatusTypeDef status = USBH_OK; // if(interface < phost->device.CfgDesc.bNumInterfaces) // { phost->device.current_interface = interface; USBH_UsrLog ('Switching to Interface (#%d)', interface); USBH_UsrLog ('Class : %xh', phost->device.CfgDesc.Itf_Desc[interface].bInterfaceClass ); USBH_UsrLog ('SubClass : %xh', phost->device.CfgDesc.Itf_Desc[interface].bInterfaceSubClass ); USBH_UsrLog ('Protocol : %xh', phost->device.CfgDesc.Itf_Desc[interface].bInterfaceProtocol ); // } // else // { // USBH_ErrLog ('Cannot Select This Interface.'); // status = USBH_FAIL; // } return status; }�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
Because
USBH_FindInterface(phost, phost->pActiveClass->ClassCode, 0x00, 0x00); returned 5 but phost->device.CfgDesc.bNumInterfaces just only 4.
I have a question about opening pipe too. Why they not set phost->Control.pipe_out/in after opened it
for ( ;num < max_ep; num++) { if(phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress & 0x80) { HID_Handle->InEp = (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress); HID_Handle->InPipe =\ USBH_AllocPipe(phost, HID_Handle->InEp); /* Open pipe for IN endpoint */ USBH_OpenPipe (phost, HID_Handle->InPipe, HID_Handle->InEp, phost->device.address, phost->device.speed, USB_EP_TYPE_INTR, HID_Handle->length); //phost->Control.pipe_in=HID_Handle->InPipe; USBH_LL_SetToggle (phost, HID_Handle->InPipe, 0); } else { HID_Handle->OutEp = (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress); HID_Handle->OutPipe =\ USBH_AllocPipe(phost, HID_Handle->OutEp); /* Open pipe for OUT endpoint */ USBH_OpenPipe (phost, HID_Handle->OutPipe, HID_Handle->OutEp, phost->device.address, phost->device.speed, USB_EP_TYPE_INTR, HID_Handle->length); //phost->Control.pipe_out=HID_Handle->OutPipe; USBH_LL_SetToggle (phost, HID_Handle->OutPipe, 0); } } �?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
After i done all of those i still got stuck at USBH_HID_ClassRequest() in case HID_REQ_GET_HID_DESC:
case HID_REQ_GET_HID_DESC: /* Get HID Desc */ if (USBH_HID_GetHIDDescriptor (phost, USB_HID_DESC_SIZE)== USBH_OK) { USBH_HID_ParseHIDDesc(&HID_Handle->HID_Desc, phost->device.Data); HID_Handle->ctl_state = HID_REQ_GET_REPORT_DESC; } break;�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
USBH_HID_GetHIDDescriptor can't return USBH_OK.
USBH_HID_SetProtocol (phost, 0) in 'case HID_REQ_SET_PROTOCOL:' got the same problem too.
So i bypassed those then get into USBH_HID_Process() but it stuck in 'case HID_IDLE:' too.
case HID_IDLE: if(USBH_HID_GetReport (phost, 0x01, 0, HID_Handle->pData, HID_Handle->length) == USBH_OK) { fifo_write(&HID_Handle->fifo, HID_Handle->pData, HID_Handle->length); HID_Handle->state = HID_SYNC; } break;�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
i wish that it could be easy as said ;)
Do you have or know where i can get some documents about USB host, i did some search but only got 2 site :
http://www.beyondlogic.org/usbnutshell/usb6.shtml#StandardDeviceRequests
http://www.usbmadesimple.co.uk/ums_5.htm
and both of those don't explain about parameters of USB HID Request.
i couldn't get the meanning of wValue and wIndex in this function:
USBH_StatusTypeDef USBH_HID_GetReport (USBH_HandleTypeDef *phost, uint8_t reportType, uint8_t reportId, uint8_t* reportBuff, uint8_t reportLen) { phost->Control.setup.b.bmRequestType = USB_D2H | USB_REQ_RECIPIENT_INTERFACE |\ USB_REQ_TYPE_CLASS; phost->Control.setup.b.bRequest = USB_HID_GET_REPORT; phost->Control.setup.b.wValue.w = (reportType << 8 ) | reportId; phost->Control.setup.b.wIndex.w = 0; phost->Control.setup.b.wLength.w = reportLen; return USBH_CtlReq(phost, reportBuff , reportLen ); }�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
________________
Attachments : debug.png : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006Hyyk&d=%2Fa%2F0X0000000bEo%2FMj53S8USgoKRhQ7pbWjga5ZaZIdmAueJL9P6yHWw27M&asPdf=false