summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ports/atmel-samd/common-hal/usb_hid/__init__.c58
-rw-r--r--ports/atmel-samd/tools/gen_usb_descriptor.py42
-rw-r--r--ports/atmel-samd/tools/hid_report_descriptors.py239
m---------tools/usb_descriptor0
4 files changed, 324 insertions, 15 deletions
diff --git a/ports/atmel-samd/common-hal/usb_hid/__init__.c b/ports/atmel-samd/common-hal/usb_hid/__init__.c
index 8cbccbb4c..c9157519f 100644
--- a/ports/atmel-samd/common-hal/usb_hid/__init__.c
+++ b/ports/atmel-samd/common-hal/usb_hid/__init__.c
@@ -35,12 +35,27 @@
#include "genhdr/autogen_usb_descriptor.h"
// Buffers are report size + 1 to include the Report ID prefix byte if needed.
+#ifdef USB_HID_REPORT_LENGTH_KEYBOARD
static uint8_t keyboard_report_buffer[USB_HID_REPORT_LENGTH_KEYBOARD + 1];
+#endif
+#ifdef USB_HID_REPORT_ID_MOUSE
static uint8_t mouse_report_buffer[USB_HID_REPORT_LENGTH_MOUSE + 1];
+#endif
+#ifdef USB_HID_REPORT_ID_CONSUMER
static uint8_t consumer_report_buffer[USB_HID_REPORT_LENGTH_CONSUMER + 1];
+#endif
+#ifdef USB_HID_REPORT_ID_SYS_CONTROL
static uint8_t sys_control_report_buffer[USB_HID_REPORT_LENGTH_SYS_CONTROL + 1];
+#endif
+#ifdef USB_HID_REPORT_ID_GAMEPAD
+static uint8_t gamepad_report_buffer[USB_HID_REPORT_LENGTH_GAMEPAD + 1];
+#endif
+#ifdef USB_HID_REPORT_ID_DIGITIZER
+static uint8_t digitizer_report_buffer[USB_HID_REPORT_LENGTH_DIGITIZER + 1];
+#endif
usb_hid_device_obj_t usb_hid_devices[USB_HID_NUM_DEVICES] = {
+#ifdef USB_HID_REPORT_LENGTH_KEYBOARD
{
.base = { .type = &usb_hid_device_type },
.report_buffer = keyboard_report_buffer,
@@ -50,6 +65,8 @@ usb_hid_device_obj_t usb_hid_devices[USB_HID_NUM_DEVICES] = {
.usage_page = 0x01,
.usage = 0x06,
},
+#endif
+#ifdef USB_HID_REPORT_ID_MOUSE
{
.base = { .type = &usb_hid_device_type },
.report_buffer = mouse_report_buffer,
@@ -59,6 +76,8 @@ usb_hid_device_obj_t usb_hid_devices[USB_HID_NUM_DEVICES] = {
.usage_page = 0x01,
.usage = 0x02,
},
+#endif
+#ifdef USB_HID_REPORT_ID_CONSUMER
{
.base = { .type = &usb_hid_device_type },
.report_buffer = consumer_report_buffer,
@@ -68,6 +87,8 @@ usb_hid_device_obj_t usb_hid_devices[USB_HID_NUM_DEVICES] = {
.usage_page = 0x0C,
.usage = 0x01,
},
+#endif
+#ifdef USB_HID_REPORT_ID_SYS_CONTROL
{
.base = { .type = &usb_hid_device_type },
.report_buffer = sys_control_report_buffer,
@@ -77,6 +98,29 @@ usb_hid_device_obj_t usb_hid_devices[USB_HID_NUM_DEVICES] = {
.usage_page = 0x01,
.usage = 0x80,
},
+#endif
+#ifdef USB_HID_REPORT_ID_GAMEPAD
+ {
+ .base = { .type = &usb_hid_device_type },
+ .report_buffer = gamepad_report_buffer,
+ .endpoint = USB_HID_ENDPOINT_IN,
+ .report_id = USB_HID_REPORT_ID_GAMEPAD,
+ .report_length = USB_HID_REPORT_LENGTH_GAMEPAD,
+ .usage_page = 0x01,
+ .usage = 0x05,
+ },
+#endif
+#ifdef USB_HID_REPORT_ID_DIGITIZER
+ {
+ .base = { .type = &usb_hid_device_type },
+ .report_buffer = digitizer_report_buffer,
+ .endpoint = USB_HID_ENDPOINT_IN,
+ .report_id = USB_HID_REPORT_ID_DIGITIZER,
+ .report_length = USB_HID_REPORT_LENGTH_DIGITIZER,
+ .usage_page = 0x0D,
+ .usage = 0x02,
+ },
+#endif
};
@@ -86,9 +130,23 @@ mp_obj_tuple_t common_hal_usb_hid_devices = {
},
.len = USB_HID_NUM_DEVICES,
.items = {
+#if USB_HID_NUM_DEVICES >= 1
(mp_obj_t) &usb_hid_devices[0],
+#endif
+#if USB_HID_NUM_DEVICES >= 2
(mp_obj_t) &usb_hid_devices[1],
+#endif
+#if USB_HID_NUM_DEVICES >= 3
(mp_obj_t) &usb_hid_devices[2],
+#endif
+#if USB_HID_NUM_DEVICES >= 4
(mp_obj_t) &usb_hid_devices[3],
+#endif
+#if USB_HID_NUM_DEVICES >= 5
+ (mp_obj_t) &usb_hid_devices[4],
+#endif
+#if USB_HID_NUM_DEVICES >= 6
+ (mp_obj_t) &usb_hid_devices[5],
+#endif
}
};
diff --git a/ports/atmel-samd/tools/gen_usb_descriptor.py b/ports/atmel-samd/tools/gen_usb_descriptor.py
index 4669110fb..efa8124bf 100644
--- a/ports/atmel-samd/tools/gen_usb_descriptor.py
+++ b/ports/atmel-samd/tools/gen_usb_descriptor.py
@@ -7,6 +7,7 @@ import sys
sys.path.append("../../tools/usb_descriptor")
from adafruit_usb_descriptor import cdc, hid, msc, standard, util
+import hid_report_descriptors
parser = argparse.ArgumentParser(description='Generate USB descriptors.')
parser.add_argument('--manufacturer', type=str,
@@ -122,19 +123,29 @@ msc_interfaces = [
standard.EndpointDescriptor(
description="MSC in",
bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN,
- bmAttributes=standard.EndpointDescriptor.TYPE_BULK),
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK,
+ bInterval=0),
standard.EndpointDescriptor(
description="MSC out",
bEndpointAddress=0x1 | standard.EndpointDescriptor.DIRECTION_OUT,
- bmAttributes=standard.EndpointDescriptor.TYPE_BULK)
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK,
+ bInterval=0)
]
)
]
-hid_report_descriptor = hid.ReportDescriptor.MOUSE_KEYBOARD_CONSUMER_SYS_CONTROL_REPORT
-hid_report_ids = hid.ReportDescriptor.REPORT_IDS
-hid_report_lengths = hid.ReportDescriptor.REPORT_LENGTHS
-hid_max_report_length = max(hid_report_lengths.values())
+# Include only these HID devices.
+# DIGITIZER works on Linux but conflicts with MOUSE, so leave it out for now.
+hid_devices = ("KEYBOARD", "MOUSE", "CONSUMER", "GAMEPAD")
+
+combined_hid_report_descriptor = hid.ReportDescriptor(
+ description="MULTIDEVICE",
+ report_descriptor=b''.join(
+ hid_report_descriptors.REPORT_DESCRIPTORS[name].report_descriptor for name in hid_devices ))
+
+hid_report_ids_dict = { name: hid_report_descriptors.REPORT_IDS[name] for name in hid_devices }
+hid_report_lengths_dict = { name: hid_report_descriptors.REPORT_LENGTHS[name] for name in hid_devices }
+hid_max_report_length = max(hid_report_lengths_dict.values())
# ASF4 expects keyboard and generic devices to have both in and out endpoints,
# and will fail (possibly silently) if both are not supplied.
@@ -142,12 +153,13 @@ hid_endpoint_in_descriptor = standard.EndpointDescriptor(
description="HID in",
bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN,
bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT,
- bInterval=0x02)
+ bInterval=10)
hid_endpoint_out_descriptor = standard.EndpointDescriptor(
description="HID out",
bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_OUT,
- bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT)
+ bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT,
+ bInterval=10)
hid_interfaces = [
standard.InterfaceDescriptor(
@@ -159,7 +171,7 @@ hid_interfaces = [
subdescriptors=[
hid.HIDDescriptor(
description="HID",
- wDescriptorLength=len(bytes(hid_report_descriptor))),
+ wDescriptorLength=len(bytes(combined_hid_report_descriptor))),
hid_endpoint_in_descriptor,
hid_endpoint_out_descriptor,
]
@@ -274,7 +286,7 @@ uint8_t hid_report_descriptor[{HID_REPORT_DESCRIPTOR_LENGTH}];
"""
.format(SERIAL_NUMBER_OFFSET=serial_number_offset,
SERIAL_NUMBER_LENGTH=args.serial_number_length,
- HID_REPORT_DESCRIPTOR_LENGTH=len(bytes(hid_report_descriptor)),
+ HID_REPORT_DESCRIPTOR_LENGTH=len(bytes(combined_hid_report_descriptor)),
HID_ENDPOINT_IN_ADDRESS=hex(hid_endpoint_in_descriptor.bEndpointAddress),
HID_ENDPOINT_OUT_ADDRESS=hex(hid_endpoint_out_descriptor.bEndpointAddress)))
@@ -294,7 +306,7 @@ for interface in interfaces:
h_file.write("\n")
# #define the report ID's used in the combined HID descriptor
-for name, id in hid_report_ids.items():
+for name, id in hid_report_ids_dict.items():
h_file.write("""\
#define USB_HID_REPORT_ID_{NAME} {ID}
""".format(NAME=name,
@@ -303,7 +315,7 @@ for name, id in hid_report_ids.items():
h_file.write("\n")
# #define the report sizes used in the combined HID descriptor
-for name, length in hid_report_lengths.items():
+for name, length in hid_report_lengths_dict.items():
h_file.write("""\
#define USB_HID_REPORT_LENGTH_{NAME} {LENGTH}
""".format(NAME=name,
@@ -314,7 +326,7 @@ h_file.write("\n")
h_file.write("""\
#define USB_HID_NUM_DEVICES {NUM_DEVICES}
#define USB_HID_MAX_REPORT_LENGTH {MAX_LENGTH}
-""".format(NUM_DEVICES=len(hid_report_lengths),
+""".format(NUM_DEVICES=len(hid_report_lengths_dict),
MAX_LENGTH=hid_max_report_length))
@@ -322,9 +334,9 @@ h_file.write("""\
# Write out the report descriptor and info
c_file.write("""\
uint8_t hid_report_descriptor[{HID_DESCRIPTOR_LENGTH}] = {{
-""".format(HID_DESCRIPTOR_LENGTH=len(bytes(hid_report_descriptor))))
+""".format(HID_DESCRIPTOR_LENGTH=len(bytes(combined_hid_report_descriptor))))
-for b in bytes(hid_report_descriptor):
+for b in bytes(combined_hid_report_descriptor):
c_file.write("0x{:02x}, ".format(b))
c_file.write("""
};
diff --git a/ports/atmel-samd/tools/hid_report_descriptors.py b/ports/atmel-samd/tools/hid_report_descriptors.py
new file mode 100644
index 000000000..f3b28ebcf
--- /dev/null
+++ b/ports/atmel-samd/tools/hid_report_descriptors.py
@@ -0,0 +1,239 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Dan Halbert for Adafruit Industries
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import struct
+
+"""
+HID specific descriptors
+========================
+
+* Author(s): Dan Halbert
+"""
+
+from adafruit_usb_descriptor import hid
+
+REPORT_IDS = {
+ "KEYBOARD" : 1,
+ "MOUSE" : 2,
+ "CONSUMER" : 3,
+ "SYS_CONTROL" : 4,
+ "GAMEPAD" : 5,
+ "DIGITIZER" : 6,
+ }
+
+# Byte count for each kind of report. Length does not include report ID in first byte.
+REPORT_LENGTHS = {
+ "KEYBOARD" : 8,
+ "MOUSE" : 4,
+ "CONSUMER" : 2,
+ "SYS_CONTROL" : 1,
+ "GAMEPAD" : 6,
+ "DIGITIZER" : 5,
+ }
+
+KEYBOARD_WITH_ID = hid.ReportDescriptor(
+ description="KEYBOARD",
+ report_descriptor=bytes([
+ # Regular keyboard
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x06, # Usage (Keyboard)
+ 0xA1, 0x01, # Collection (Application)
+ 0x85, REPORT_IDS["KEYBOARD"], # Report ID (1)
+ 0x05, 0x07, # Usage Page (Keyboard)
+ 0x19, 224, # Usage Minimum (224)
+ 0x29, 231, # Usage Maximum (231)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x25, 0x01, # Logical Maximum (1)
+ 0x75, 0x01, # Report Size (1)
+ 0x95, 0x08, # Report Count (8)
+ 0x81, 0x02, # Input (Data, Variable, Absolute)
+ 0x81, 0x01, # Input (Constant)
+ 0x19, 0x00, # Usage Minimum (0)
+ 0x29, 101, # Usage Maximum (101)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x25, 101, # Logical Maximum (101)
+ 0x75, 0x08, # Report Size (8)
+ 0x95, 0x06, # Report Count (6)
+ 0x81, 0x00, # Input (Data, Array)
+ 0x05, 0x08, # Usage Page (LED)
+ 0x19, 0x01, # Usage Minimum (1)
+ 0x29, 0x05, # Usage Maximum (5)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x25, 0x01, # Logical Maximum (1)
+ 0x75, 0x01, # Report Size (1)
+ 0x95, 0x05, # Report Count (5)
+ 0x91, 0x02, # Output (Data, Variable, Absolute)
+ 0x95, 0x03, # Report Count (3)
+ 0x91, 0x01, # Output (Constant)
+ 0xC0, # End Collection
+ ]))
+
+MOUSE_WITH_ID = hid.ReportDescriptor(
+ description="MOUSE",
+ report_descriptor=bytes([
+ # Regular mouse
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x02, # Usage (Mouse)
+ 0xA1, 0x01, # Collection (Application)
+ 0x09, 0x01, # Usage (Pointer)
+ 0xA1, 0x00, # Collection (Physical)
+ 0x85, REPORT_IDS["MOUSE"], # Report ID (n)
+ 0x05, 0x09, # Usage Page (Button)
+ 0x19, 0x01, # Usage Minimum (0x01)
+ 0x29, 0x05, # Usage Maximum (0x05)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x25, 0x01, # Logical Maximum (1)
+ 0x95, 0x05, # Report Count (5)
+ 0x75, 0x01, # Report Size (1)
+ 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95, 0x01, # Report Count (1)
+ 0x75, 0x03, # Report Size (3)
+ 0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x30, # Usage (X)
+ 0x09, 0x31, # Usage (Y)
+ 0x15, 0x81, # Logical Minimum (-127)
+ 0x25, 0x7F, # Logical Maximum (127)
+ 0x75, 0x08, # Report Size (8)
+ 0x95, 0x02, # Report Count (2)
+ 0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+ 0x09, 0x38, # Usage (Wheel)
+ 0x15, 0x81, # Logical Minimum (-127)
+ 0x25, 0x7F, # Logical Maximum (127)
+ 0x75, 0x08, # Report Size (8)
+ 0x95, 0x01, # Report Count (1)
+ 0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, # End Collection
+ 0xC0, # End Collection
+ ]))
+
+CONSUMER_WITH_ID = hid.ReportDescriptor(
+ description="CONSUMER",
+ report_descriptor=bytes([
+ # Consumer ("multimedia") keys
+ 0x05, 0x0C, # Usage Page (Consumer)
+ 0x09, 0x01, # Usage (Consumer Control)
+ 0xA1, 0x01, # Collection (Application)
+ 0x85, REPORT_IDS["CONSUMER"], # Report ID (n)
+ 0x75, 0x10, # Report Size (16)
+ 0x95, 0x01, # Report Count (1)
+ 0x15, 0x01, # Logical Minimum (1)
+ 0x26, 0x8C, 0x02, # Logical Maximum (652)
+ 0x19, 0x01, # Usage Minimum (Consumer Control)
+ 0x2A, 0x8C, 0x02, # Usage Maximum (AC Send)
+ 0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, # End Collection
+ ]))
+
+SYS_CONTROL_WITH_ID = hid.ReportDescriptor(
+ description="SYS_CONTROL",
+ report_descriptor=bytes([
+ # Power controls
+ 0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x80, # Usage (Sys Control)
+ 0xA1, 0x01, # Collection (Application)
+ 0x85, REPORT_IDS["SYS_CONTROL"], # Report ID (n)
+ 0x75, 0x02, # Report Size (2)
+ 0x95, 0x01, # Report Count (1)
+ 0x15, 0x01, # Logical Minimum (1)
+ 0x25, 0x03, # Logical Maximum (3)
+ 0x09, 0x82, # Usage (Sys Sleep)
+ 0x09, 0x81, # Usage (Sys Power Down)
+ 0x09, 0x83, # Usage (Sys Wake Up)
+ 0x81, 0x60, # Input (Data,Array,Abs,No Wrap,Linear,No Preferred State,Null State)
+ 0x75, 0x06, # Report Size (6)
+ 0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, # End Collection
+ ]))
+
+GAMEPAD_WITH_ID = hid.ReportDescriptor(
+ description="GAMEPAD",
+ report_descriptor=bytes([
+ # Gamepad with 16 buttons and two joysticks
+ 0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x05, # Usage (Game Pad)
+ 0xA1, 0x01, # Collection (Application)
+ 0x85, REPORT_IDS["GAMEPAD"], # Report ID (n)
+ 0x05, 0x09, # Usage Page (Button)
+ 0x19, 0x01, # Usage Minimum (Button 1)
+ 0x29, 0x10, # Usage Maximum (Button 16)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x25, 0x01, # Logical Maximum (1)
+ 0x75, 0x01, # Report Size (1)
+ 0x95, 0x10, # Report Count (16)
+ 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
+ 0x15, 0x81, # Logical Minimum (-127)
+ 0x25, 0x7F, # Logical Maximum (127)
+ 0x09, 0x30, # Usage (X)
+ 0x09, 0x31, # Usage (Y)
+ 0x09, 0x32, # Usage (Z)
+ 0x09, 0x35, # Usage (Rz)
+ 0x75, 0x08, # Report Size (8)
+ 0x95, 0x04, # Report Count (4)
+ 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, # End Collection
+ ]))
+
+DIGITIZER_WITH_ID = hid.ReportDescriptor(
+ description="DIGITIZER",
+ report_descriptor=bytes([
+ # Digitizer (used as an absolute pointer)
+ 0x05, 0x0D, # Usage Page (Digitizers)
+ 0x09, 0x02, # Usage (Pen)
+ 0xA1, 0x01, # Collection (Application)
+ 0x85, REPORT_IDS["DIGITIZER"], # Report ID (n)
+ 0x09, 0x01, # Usage (Stylus)
+ 0xA1, 0x00, # Collection (Physical)
+ 0x09, 0x32, # Usage (In-Range)
+ 0x09, 0x42, # Usage (Tip Switch)
+ 0x09, 0x44, # Usage (Barrel Switch)
+ 0x09, 0x45, # Usage (Eraser Switch)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x25, 0x01, # Logical Maximum (1)
+ 0x75, 0x01, # Report Size (1)
+ 0x95, 0x04, # Report Count (4)
+ 0x81, 0x02, # Input (Data,Var,Abs)
+ 0x75, 0x04, # Report Size (4) -- Filler
+ 0x95, 0x01, # Report Count (1) -- Filler
+ 0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x26, 0xff, 0x7f, # Logical Maximum (32767)
+ 0x09, 0x30, # Usage (X)
+ 0x09, 0x31, # Usage (Y)
+ 0x75, 0x10, # Report Size (16)
+ 0x95, 0x02, # Report Count (2)
+ 0x81, 0x02, # Input (Data,Var,Abs)
+ 0xC0, # End Collection
+ 0xC0, # End Collection
+ ]))
+
+# Byte count for each kind of report. Length does not include report ID in first byte.
+REPORT_DESCRIPTORS = {
+ "KEYBOARD" : KEYBOARD_WITH_ID,
+ "MOUSE" : MOUSE_WITH_ID,
+ "CONSUMER" : CONSUMER_WITH_ID,
+ "SYS_CONTROL" : SYS_CONTROL_WITH_ID,
+ "GAMEPAD" : GAMEPAD_WITH_ID,
+ "DIGITIZER" : DIGITIZER_WITH_ID,
+ }
diff --git a/tools/usb_descriptor b/tools/usb_descriptor
-Subproject 0ccd1935d666bcffc3fd5941ba06bd9273d72e0
+Subproject 2507847031a0395465956539253cbfa27f87511