{"id":2915,"date":"2020-05-20T10:19:00","date_gmt":"2020-05-20T08:19:00","guid":{"rendered":"https:\/\/www.basyskom.de\/?p=2915"},"modified":"2020-05-28T15:01:11","modified_gmt":"2020-05-28T13:01:11","slug":"iot-hub-device-provisioning-service","status":"publish","type":"post","link":"https:\/\/www.basyskom.de\/en\/iot-hub-device-provisioning-service\/","title":{"rendered":"IoT Hub Device Provisioning Service, bring your IoT Devices into the Cloud. (Part 4 of 4)"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"2915\" class=\"elementor elementor-2915\" data-elementor-post-type=\"post\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-5fc2275 elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no\" data-id=\"5fc2275\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-248117b\" data-id=\"248117b\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-cf89f56 elementor-widget elementor-widget-text-editor\" data-id=\"cf89f56\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h2 class=\"western\">Introduction<\/h2><p>While it is possible to fully provision an IoT device during production, there are several problems related to load balancing and selecting an IoT Hub in the geographical region where the device is going be used. It would be helpful if the device would just need to know where to get provisioning information and how to authenticate to the provisioning provider.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-bf7aec1 elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no\" data-id=\"bf7aec1\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-80f740b\" data-id=\"80f740b\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-inner-section elementor-element elementor-element-8e1935a elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no\" data-id=\"8e1935a\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-33 elementor-inner-column elementor-element elementor-element-2cfc18f\" data-id=\"2cfc18f\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-33 elementor-inner-column elementor-element elementor-element-38b33d1\" data-id=\"38b33d1\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-e48fc48 elementor-view-default elementor-invisible elementor-widget elementor-widget-icon\" data-id=\"e48fc48\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;_animation&quot;:&quot;swing&quot;}\" data-widget_type=\"icon.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-icon-wrapper\">\n\t\t\t<div class=\"elementor-icon\">\n\t\t\t<i aria-hidden=\"true\" class=\"hm hm-key\"><\/i>\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t<div class=\"elementor-column elementor-col-33 elementor-inner-column elementor-element elementor-element-8332d4b\" data-id=\"8332d4b\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-b0d618b elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no\" data-id=\"b0d618b\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-99d2b1c\" data-id=\"99d2b1c\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-2da7b00 elementor-widget elementor-widget-text-editor\" data-id=\"2da7b00\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h2 class=\"western\">The Azure IoT Hub Device Provisioning Service<\/h2><p><img decoding=\"async\" src=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2020\/05\/491c15dc-7ceb-4925-b6ad-d85127f38a43.png\" alt=\"device provisioning service\" width=\"160\" height=\"80\" align=\"right\" \/>The <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/iot-dps\/about-iot-dps\" target=\"_blank\" rel=\"noopener\">Device Provisioning Service<\/a> is a managed service running in the Azure cloud which supports automatic provisioning of IoT devices for IoT Hub.<\/p><p>The following sections will give a short introduction to the most important features of the Device Provisioning Service.<\/p><h3 class=\"western\">Identifying a Device Provisioning Service instance<\/h3><p>Every Device Provisioning Service instance is assigned a so called <code>ID scope<\/code> on creation which is unique and does not change during the entire lifetime of the instance. It must be used by the IoT devices when querying the Device Provisioning Service.<\/p><h3 class=\"western\">Enrollments<\/h3><p>The basic requirement for provisioning a device via Device Provisioning Service is an <code>enrollment<\/code>.<\/p><p>Devices can be enrolled in groups using <code>X.509<\/code> certificates or static keys. This means that all devices having a certificate signed by the same CA or a static key <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/iot-dps\/concepts-symmetric-key-attestation\" target=\"_blank\" rel=\"noopener\">derived from<\/a> the same master key have the right to query provisioning information from the Device Provisioning Service.<\/p><p>Individual enrollments are enrollments for a single device. For devices with a Trusted Platform Module (TPM), an individual enrollment can also use <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/iot-dps\/concepts-tpm-attestation\" target=\"_blank\" rel=\"noopener\">TPM attestation<\/a>. This allows a device having a TPM with a certain <code>endorsement key<\/code> to connect to the Device Provisioning Service. Challenge-response authentication is employed to verify that the device really is in possession of the <code>private key<\/code> belonging to the <code>endorsement key<\/code> and at the same time, the device obtains a <code>token<\/code> which is used to retrieve the provisioning data from the Device Provisioning Service as well as for authentication to the IoT Hub.<\/p><p>An individual enrollment with TPM attestation is the best way to use the Device Provisioning Service because there is no static key to be stored on the device and no process for creating and renewing certificates is required. The only additional steps required for provisioning a device during production or the end test is to store the <code>ID scope<\/code> of the Device Provisioning Service on the device and to read out the public part of the TPM&#8217;s <code>endorsement key<\/code>.<\/p><p>The extracted public key is used in a later step to create an individual enrollment for the device in the Device Provisioning Service. This could be made an automatic process, for example in cooperation with an ERP system.<\/p><h3 class=\"western\">The default device twin<\/h3><p>The values for the <code>tags<\/code> and <code>desired<\/code> properties of the initial device twin are individually configurable for every enrollment.<\/p><h3 class=\"western\">IoT Hub assignment<\/h3><p>The Device Provisioning Service can be linked to several IoT Hubs in different geographical regions.<\/p><p>An enrollment can be assigned to one of the linked IoT Hubs or a subset of the linked IoT Hubs based on different policies<\/p><ul><li>Static assignment to a certain IoT Hub<\/li><li>Lowest latency<\/li><li>Evenly weighted distribution<\/li><li>Call a custom assignment function on an Azure Function App<\/li><\/ul><p>The behavior of the Device Provisioning Service on subsequent requests by the same device is also configurable. The device can either always use its first IoT Hub or be assigned to a new IoT Hub according to the assignment policy. Reprovisioning can be configured to migrate the device twin or to initialize the device twin on the new IoT Hub with the default value.<\/p><h3 class=\"western\">Enabling and disabling enrollments<\/h3><p>Enrollment groups and individual enrollments can be temporarily disabled, for example to block misbehaving devices. This does not prevent the device from connecting to the IoT Hub it is currently provisioned to, the IoT Hub registration belonging to the device must also be disabled if a device is to be locked out completely.<\/p><h3 class=\"western\">Provisioning Information<\/h3><p>If the client has successfully established a connection with the Device Provisioning Service, it receives the device id and the URL of the IoT Hub it has been assigned to. This information is used to connect to the IoT Hub where additional configuration data is stored in the device twin.<\/p><p>By combining the Device Provisioning Service with configuration data in the device twin, an IoT device can be bootstrapped just from knowing how to reach the Device Provisioning Service.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-786c65a elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no\" data-id=\"786c65a\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-a8fcc55\" data-id=\"a8fcc55\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-951c728 elementor-view-default elementor-widget elementor-widget-icon\" data-id=\"951c728\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"icon.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-icon-wrapper\">\n\t\t\t<div class=\"elementor-icon\">\n\t\t\t<i aria-hidden=\"true\" class=\"hm hm-unlock\"><\/i>\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-d8992f5 elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no\" data-id=\"d8992f5\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-8794dde\" data-id=\"8794dde\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-bcbc23c elementor-widget elementor-widget-text-editor\" data-id=\"bcbc23c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h2 class=\"western\">Client example<\/h2><p>To demonstrate how TPM attestation is done with the <a href=\"https:\/\/github.com\/Azure\/azure-iot-sdk-c\" target=\"_blank\" rel=\"noopener\">Azure IoT C SDK<\/a>, we will extend our Qt based C++ IoT Hub\u00a0<a href=\"https:\/\/www.basyskom.de\/2020\/protobuf-for-iot\/\" target=\"_blank\" rel=\"noopener\">client example<\/a>.<\/p><p>The full example code is hosted on <a href=\"https:\/\/github.com\/basysKom\/iot_from_scratch\/tree\/master\/Part4_Device_Provisioning_Service\" target=\"_blank\" rel=\"noopener\">github<\/a>.<\/p><p>We will use a simulated TPM which can be downloaded <a href=\"https:\/\/sourceforge.net\/projects\/ibmswtpm2\/\" target=\"_blank\" rel=\"noopener\">here<\/a> for Linux users. Windows users can find a pre-built TPM simulator in the <code>azure-iot-sdk-c<\/code> repository in the <code>provisioning_client\/deps\/utpm\/tools\/tpm_simulator\u00a0<\/code>directory.<\/p><p>The Azure IoT SDK must be built with the CMake options <code>use_tpm_simulator<\/code> and <code>use_prov_client<\/code> set to <code>ON<\/code>.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f35ab52 elementor-widget elementor-widget-text-editor\" data-id=\"f35ab52\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>Changes to the IotHubClient class<\/h3><p>The <code>IotHubClient<\/code> class is extended with two Device Provisioning Service specific callbacks, the new member variables\u00a0<code>mProvisioningHandle<\/code> and <code>mIdScope<\/code>\u00a0and several helper functions for connection establishment.<br \/>The information passed to <code>init()<\/code> is changed to the ID scope of the Device Provisioning Service.<\/p><p>The new method named <code>queryTpmInformation()<\/code> is used to retrieve and print the values needed for creating an enrollment in the Device Provisioning Service.<\/p><p>The destructor and <code>doWork()<\/code> are extended to deal with the provisioning client.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-dcfbe5c elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"dcfbe5c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>#include &lt;iothub.h&gt;\n#include &lt;iothub_device_client_ll.h&gt;\n#include &lt;iothub_client_options.h&gt;\n#include &lt;iothub_message.h&gt;\n#include &lt;iothubtransportamqp.h&gt;\n\n#include &quot;azure_prov_client\/prov_device_ll_client.h&quot;\n#include &lt;azure_prov_client\/prov_transport_amqp_client.h&gt;\n#include &lt;azure_prov_client\/prov_security_factory.h&gt;\n#include &lt;azure_prov_client\/prov_auth_client.h&gt;\n\nclass IotHubClient : public QObject\n{\n    Q_OBJECT\n\npublic:\n    IotHubClient(QObject *parent = nullptr);\n    ~IotHubClient();\n\n    bool init(const QString &amp;idScope);\n\n    bool sendMessage(int id, const QByteArray &amp;data);\n    bool updateDeviceTwin(const QJsonObject &amp;reported);\n\n    bool connected() const;\n\n    bool queryTpmInformation();\n\nsignals:\n    void connectedChanged(bool connected);\n    void deviceTwinUpdated(int statusCode);\n    void messageStatusChanged(int id, bool success);\n    void desiredObjectChanged(QJsonObject desired);\n\nprivate:\n    \/\/ Callbacks for the DPS client\n    static void registerDeviceCallback(PROV_DEVICE_RESULT result, const char* iotHubUri,\n                                  const char* deviceId, void* context);\n    static void registrationStatusCallback(PROV_DEVICE_REG_STATUS registrationStatus, void* context);\n\n    \/\/ Callbacks for the IoT Hub client\n    static void connectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result,\n                                         IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason,\n                                         void* context);\n    static void sendConfirmCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* context);\n    static void deviceTwinCallback(DEVICE_TWIN_UPDATE_STATE updateState, const unsigned char* payload,\n                                   size_t size, void* context);\n    static void reportedStateCallback(int statusCode, void* context);\n\n    \/\/ Call the IOT SDK&#039;s DoWork functions\n    void doWork();\n\n    bool requestProvisioningData();\n    void initializeIotHub(const QString &amp;iotHubUri, const QString &amp;deviceId);\n\n    void setConnected(bool connected);\n\n    struct MessageContext {\n        MessageContext(IotHubClient *client, int id, IOTHUB_MESSAGE_HANDLE message) {\n            this-&gt;client = client;\n            this-&gt;id = id;\n            this-&gt;message = message;\n        }\n        IotHubClient *client;\n        int id;\n        IOTHUB_MESSAGE_HANDLE message;\n    };\n\n    QTimer mDoWorkTimer;\n    IOTHUB_DEVICE_CLIENT_LL_HANDLE mDeviceHandle = nullptr;\n    PROV_DEVICE_LL_HANDLE mProvisioningHandle = nullptr;\n    QString mIdScope;\n    bool mConnected = false;\n};\n\nIotHubClient::~IotHubClient()\n{\n    mDoWorkTimer.stop();\n    if (mDeviceHandle)\n        IoTHubDeviceClient_LL_Destroy(mDeviceHandle);\n    IoTHub_Deinit();\n\n    if (mProvisioningHandle)\n        Prov_Device_LL_Destroy(mProvisioningHandle);\n    prov_dev_security_deinit();\n}\n\nvoid IotHubClient::doWork()\n{\n    if (mDeviceHandle)\n        IoTHubDeviceClient_LL_DoWork(mDeviceHandle);\n    if (mProvisioningHandle)\n        Prov_Device_LL_DoWork(mProvisioningHandle);\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f7fe55b elementor-widget elementor-widget-text-editor\" data-id=\"f7fe55b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>The device registration callback<\/h3><p>The device registration callback is called with the result of the registration process. On success, the device&#8217;s <code>device id<\/code> and the <code>IoT Hub URI<\/code> are also passed as parameters.<\/p><p>Our implementation triggers the initialization of the IoT Hub client once the provisioning information has been successfully retrieved. In case of an error, a retry is scheduled.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c5d5c78 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"c5d5c78\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>void IotHubClient::registerDeviceCallback(PROV_DEVICE_RESULT result, const char *iotHubUri,\n                                          const char *deviceId, void *context)\n{\n    qDebug() &lt;&lt; &quot;Register device callback with result&quot; &lt;&lt; result;\n\n    auto client = static_cast&lt;IotHubClient *&gt;(context);\n\n    if (result == PROV_DEVICE_RESULT_OK) {\n        qDebug() &lt;&lt; &quot;Provisioning information received&quot;;\n        qDebug() &lt;&lt; &quot;IoT Hub:&quot; &lt;&lt; iotHubUri &lt;&lt; &quot;Device id:&quot; &lt;&lt; deviceId;\n\n        const auto uri = QString::fromUtf8(iotHubUri);\n        const auto id = QString::fromUtf8(deviceId);\n\n        QTimer::singleShot(1000, client, [=]() {\n            client-&gt;initializeIotHub(uri, id);\n        });\n    } else {\n        qDebug() &lt;&lt; &quot;Provisioning failed, retry&quot;;\n        QTimer::singleShot(5000, client, [=]() {\n            client-&gt;requestProvisioningData();\n        });\n    }\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-74c5c31 elementor-widget elementor-widget-text-editor\" data-id=\"74c5c31\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>The registration status callback<\/h3><p>The registration status callback is called for the different status changes during the registration process. Our implementation just prints this information.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-044d4ce elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"044d4ce\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>void IotHubClient::registrationStatusCallback(PROV_DEVICE_REG_STATUS registrationStatus, void *context)\n{\n    Q_UNUSED(context);\n    qDebug() &lt;&lt; &quot;Registration status callback with status&quot; &lt;&lt; registrationStatus;\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-414690e elementor-widget elementor-widget-text-editor\" data-id=\"414690e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>Printing provisioning information<\/h3><p>A <code>registration id<\/code> and the public part of the TPM&#8217;s <code>endorsement key<\/code> are required to create a enrollment at the Device Provisioning Service.<\/p><p>The <code>prov_auth_get_endorsement_key()<\/code> function retrieves the public part of the <code>endorsement key<\/code>.<\/p><p>The <code>registration id<\/code> is retrieved by calling <code>prov_auth_get_registration_id()<\/code>. The value is derived from the public part of the <code>endorsement key<\/code>.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-016ae14 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"016ae14\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>bool IotHubClient::queryTpmInformation()\n{\n    PROV_AUTH_HANDLE authHandle = prov_auth_create();\n    if (!authHandle) {\n        qDebug() &lt;&lt; &quot;Provisioning authentication handle creation failed&quot;;\n        return false;\n    }\n\n    QByteArray endorsementKey;\n    QString registrationId;\n    char *registrationIdBuffer = nullptr;\n\n    BUFFER_HANDLE endorsementKeyBuffer = prov_auth_get_endorsement_key(authHandle);\n\n    if (!endorsementKeyBuffer) {\n        qDebug() &lt;&lt; &quot;Failed to query endorsement key&quot;;\n        prov_auth_destroy(authHandle);\n        return false;\n    } else {\n        endorsementKey = QByteArray(reinterpret_cast&lt;const char *&gt;(BUFFER_u_char(endorsementKeyBuffer)),\n                        BUFFER_length(endorsementKeyBuffer));\n        BUFFER_delete(endorsementKeyBuffer);\n    }\n\n    registrationIdBuffer = prov_auth_get_registration_id(authHandle);\n    if (!endorsementKeyBuffer) {\n        qDebug() &lt;&lt; &quot;Failed to query registration id&quot;;\n        prov_auth_destroy(authHandle);\n        return false;\n    } else {\n        registrationId = QString::fromLatin1(registrationIdBuffer);\n        free(registrationIdBuffer);\n    }\n\n    prov_auth_destroy(authHandle);\n\n    qDebug() &lt;&lt; &quot;Registration id:&quot; &lt;&lt; registrationId;\n    qDebug() &lt;&lt; &quot;Endorsement key:&quot; &lt;&lt; endorsementKey.toBase64();\n\n    return true;\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6bc0d49 elementor-widget elementor-widget-text-editor\" data-id=\"6bc0d49\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>The modified init()<\/h3><p>After initializing the SDK, the <code>queryTpmInformation()<\/code> method is called and prints the <code>registration id<\/code> and the <code>endorsement key<\/code> to the terminal.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-dbcc359 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"dbcc359\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>bool IotHubClient::init(const QString &amp;idScope)\n{\n    if (mProvisioningHandle || mDeviceHandle) {\n        qDebug() &lt;&lt; &quot;Client is already initialized&quot;;\n        return false;\n    }\n\n    mIdScope = idScope;\n\n    auto result = IoTHub_Init();\n\n    if (result != IOTHUB_CLIENT_OK) {\n        qWarning() &lt;&lt; &quot;IoTHub_Init failed with result&quot; &lt;&lt; result;\n        return false;\n    }\n\n    result = prov_dev_security_init(SECURE_DEVICE_TYPE_TPM);\n\n    if (result != IOTHUB_CLIENT_OK) {\n        qWarning() &lt;&lt; &quot;prov_dev_security_init failed with result&quot; &lt;&lt; result;\n        return false;\n    }\n\n    queryTpmInformation();\n\n    const auto success = requestProvisioningData();\n\n    if (success)\n        mDoWorkTimer.start();\n\n    return success;\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-34f19d2 elementor-widget elementor-widget-text-editor\" data-id=\"34f19d2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Instead of constructing the IoT Hub client directly, the first step for establishing a connection is now to construct a Device Provisioning Service client to retrieve the necessary provisioning information by calling\u00a0<code>Prov_Device_LL_Create()<\/code>.<\/p><p>The following call to\u00a0<code>Prov_Device_LL_Register_Device()<\/code>\u00a0initializes the provisioning process. The two callbacks described before are passed as parameters.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-867a01e elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"867a01e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>bool IotHubClient::requestProvisioningData()\n{\n    if (mProvisioningHandle) {\n        Prov_Device_LL_Destroy(mProvisioningHandle);\n        mProvisioningHandle = nullptr;\n    }\n\n    mProvisioningHandle = Prov_Device_LL_Create(&quot;global.azure-devices-provisioning.net&quot;,\n                                                mIdScope.toLatin1().constData(),\n                                                Prov_Device_AMQP_Protocol);\n\n    if (!mProvisioningHandle) {\n        qWarning() &lt;&lt; &quot;Failed to create DPS client&quot;;\n        return false;\n    }\n\n    auto result = Prov_Device_LL_Register_Device(mProvisioningHandle, registerDeviceCallback, this,\n                                                 registrationStatusCallback, this);\n\n    if (result != PROV_DEVICE_RESULT_OK) {\n        qWarning() &lt;&lt; &quot;Failed to dispatch register device with result&quot; &lt;&lt; result;\n        return false;\n    }\n\n    return true;\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-045c4d2 elementor-widget elementor-widget-text-editor\" data-id=\"045c4d2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>Initializing the IoT Hub client<\/h3><p>First, the provisioning client must be destroyed to allow the IoT Hub client to access the TPM.<\/p><p>Instead of using <code>IoTHubDeviceClient_LL_CreateFromConnectionString()<\/code> to create the client, <code>IoTHubDeviceClient_LL_CreateFromDeviceAuth()<\/code> must be called now.<\/p><p>The rest of the initialization can be left unchanged.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8f14897 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"8f14897\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>void IotHubClient::initializeIotHub(const QString &amp;iotHubUri, const QString &amp;deviceId)\n{\n    \/\/ Destroy the provisioning client to unblock TPM access\n    if (mProvisioningHandle) {\n        Prov_Device_LL_Destroy(mProvisioningHandle);\n        mProvisioningHandle = nullptr;\n\n        prov_dev_security_deinit();\n    }\n\n    \/\/ Destroy previous client\n    if (mDeviceHandle) {\n        IoTHubDeviceClient_LL_Destroy(mDeviceHandle);\n        mDeviceHandle = nullptr;\n    }\n\n    mDeviceHandle = IoTHubDeviceClient_LL_CreateFromDeviceAuth(iotHubUri.toUtf8().constData(),\n                                                               deviceId.toUtf8().constData(),\n                                                               AMQP_Protocol);\n\n    if (!mDeviceHandle) {\n        qWarning() &lt;&lt; &quot;Failed to create client from connection string&quot;;\n        return;\n    }\n\n    qDebug() &lt;&lt; &quot;Client created&quot;;\n\n    auto result = IoTHubDeviceClient_LL_SetConnectionStatusCallback(mDeviceHandle,\n                                                                    connectionStatusCallback,\n                                                                    this);\n\n    if (result != IOTHUB_CLIENT_OK) {\n        qWarning() &lt;&lt; &quot;Failed to set connection status callback with result&quot; &lt;&lt; result;\n        return;\n    }\n\n    result = IoTHubDeviceClient_LL_SetDeviceTwinCallback(mDeviceHandle, deviceTwinCallback, this);\n\n    if (result != IOTHUB_CLIENT_OK) {\n        qWarning() &lt;&lt; &quot;Failed to set device twin callback with result&quot; &lt;&lt; result;\n        return;\n    }\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d1a7811 elementor-widget elementor-widget-text-editor\" data-id=\"d1a7811\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>Changes to main()<\/h3>\nThe connection string must be replaced by the <code>ID scope<\/code> of your Device Provisioning Service instance.\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c25ca1e elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"c25ca1e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>const auto idScope = QLatin1String(&quot;0neXXXXXXXX&quot;);\nauto result = client.init(idScope); <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f72c134 elementor-view-default elementor-widget elementor-widget-icon\" data-id=\"f72c134\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"icon.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-icon-wrapper\">\n\t\t\t<div class=\"elementor-icon\">\n\t\t\t<i aria-hidden=\"true\" class=\"hm hm-bowl\"><\/i>\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-dd2c95a elementor-widget elementor-widget-text-editor\" data-id=\"dd2c95a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>Building the example<\/h3>\nThe <code>.pro<\/code> file must be extended to include additional linker flags for the device provisioning specific libraries.\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9575c81 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"9575c81\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-solid'># Linker flags for device provisioning\nLIBS += \\\n    -lprov_device_ll_client \\\n    -lprov_auth_client \\\n    -lprov_amqp_transport \\\n    -lhsm_security_client \\\n    -lutpm \\\n    -luamqp \\\n    -laziotsharedutil \\\n    -luuid \\\n    -lmsr_riot \\\n    -lssl \\\n    -lcrypto \\\n    -lcurl \\ <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c846557 elementor-widget elementor-widget-text-editor\" data-id=\"c846557\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h3>Testing the example<\/h3><p>Start the TPM simulator and the example application.<\/p><p>If the initialization is successful, the <code>registration id<\/code> and the <code>endorsement key<\/code> are printed to the terminal. Use these values to create an individual enrollment at your Device Provisioning Service instance.<\/p><p>The example should now retrieve and print the provisioning information and then perform the IoT Hub client initialization.<\/p><p>After the client has been successfully connected, it behaves the same as described in the <a href=\"https:\/\/www.basyskom.de\/2020\/iot-hub-device-provisioning-service\/\">IoT Hub<\/a> and\u00a0<a href=\"https:\/\/www.basyskom.de\/2020\/protobuf-for-iot\/\" target=\"_blank\" rel=\"noopener\">Protobuf<\/a>\u00a0articles.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-984fd8e elementor-section-boxed elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no\" data-id=\"984fd8e\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-96cb032\" data-id=\"96cb032\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-efdf8cc elementor-view-default elementor-widget elementor-widget-icon\" data-id=\"efdf8cc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"icon.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-icon-wrapper\">\n\t\t\t<div class=\"elementor-icon\">\n\t\t\t<i aria-hidden=\"true\" class=\"hm hm-card\"><\/i>\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fd61afe elementor-widget elementor-widget-text-editor\" data-id=\"fd61afe\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h2>Conclusion<\/h2><p>The Device Provisioning Service is a good supplement to the IoT Hub for bringing IoT devices into our system and managing the load between different IoT Hub instances.<\/p><p>As demonstrated in the example above, it is easy to use the Azure IoT SDK to communicate with the Device Provisioning Service. The only information that was required to bootstrap out IoT device is the <code>ID scope<\/code> for our Device Provisioning Service instance.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>The Device Provisioning Service is a managed service running in the Azure cloud which supports automatic provisioning of IoT devices for IoT Hub.<\/p>\n<p>The following sections will give a short introduction to the most important features of the Device Provisioning Service.<\/p>","protected":false},"author":4,"featured_media":2967,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1,2,134,8],"tags":[139,137,136],"class_list":["post-2915","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein","category-blog","category-cloud","category-qt","tag-azure","tag-cloud","tag-iot"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/2915","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/comments?post=2915"}],"version-history":[{"count":70,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/2915\/revisions"}],"predecessor-version":[{"id":3532,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/2915\/revisions\/3532"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media\/2967"}],"wp:attachment":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media?parent=2915"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/categories?post=2915"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/tags?post=2915"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}