diff --git a/dev/404.html b/dev/404.html index bcb71f703..fb1b55cb2 100644 --- a/dev/404.html +++ b/dev/404.html @@ -14,7 +14,7 @@ - + diff --git a/dev/about/credits/index.html b/dev/about/credits/index.html index dab7cf4f8..13bcfc14f 100644 --- a/dev/about/credits/index.html +++ b/dev/about/credits/index.html @@ -9,7 +9,7 @@ - + Credits - IoT Hub Portal diff --git a/dev/about/issues/index.html b/dev/about/issues/index.html index 0bd228b02..ec6994ebb 100644 --- a/dev/about/issues/index.html +++ b/dev/about/issues/index.html @@ -9,7 +9,7 @@ - + Known Issues - IoT Hub Portal diff --git a/dev/about/license/index.html b/dev/about/license/index.html index 9ff7e9d3a..e31f50aaf 100644 --- a/dev/about/license/index.html +++ b/dev/about/license/index.html @@ -8,7 +8,7 @@ - + License - IoT Hub Portal diff --git a/dev/about/support/index.html b/dev/about/support/index.html index 2cadc45da..b246b4c8f 100644 --- a/dev/about/support/index.html +++ b/dev/about/support/index.html @@ -9,7 +9,7 @@ - + Support - IoT Hub Portal diff --git a/dev/aws/index.html b/dev/aws/index.html index f7811bf37..1fbd44ad4 100644 --- a/dev/aws/index.html +++ b/dev/aws/index.html @@ -9,7 +9,7 @@ - + AWS - IoT Hub Portal diff --git a/dev/azure/index.html b/dev/azure/index.html index 30f90da6b..73c073d22 100644 --- a/dev/azure/index.html +++ b/dev/azure/index.html @@ -9,7 +9,7 @@ - + Azure - IoT Hub Portal diff --git a/dev/b2c-applications/index.html b/dev/b2c-applications/index.html index 32dfecdf7..f2411e970 100644 --- a/dev/b2c-applications/index.html +++ b/dev/b2c-applications/index.html @@ -7,7 +7,7 @@ - + Azure AD B2C Tenant with applications - IoT Hub Portal diff --git a/dev/concepts/index.html b/dev/concepts/index.html index 4fc3df70c..952928159 100644 --- a/dev/concepts/index.html +++ b/dev/concepts/index.html @@ -9,7 +9,7 @@ - + Concepts - IoT Hub Portal diff --git a/dev/dev-guide/conception/diagrams/index.html b/dev/dev-guide/conception/diagrams/index.html index b466bbcb1..dd403e142 100644 --- a/dev/dev-guide/conception/diagrams/index.html +++ b/dev/dev-guide/conception/diagrams/index.html @@ -9,7 +9,7 @@ - + Diagrams - IoT Hub Portal diff --git a/dev/dev-guide/index.html b/dev/dev-guide/index.html index b07a5a3f2..268c31e69 100644 --- a/dev/dev-guide/index.html +++ b/dev/dev-guide/index.html @@ -9,7 +9,7 @@ - + Developer Guide - IoT Hub Portal diff --git a/dev/dev-guide/migrations/v3-to-v4/index.html b/dev/dev-guide/migrations/v3-to-v4/index.html index 79d41fa0a..60b801ba7 100644 --- a/dev/dev-guide/migrations/v3-to-v4/index.html +++ b/dev/dev-guide/migrations/v3-to-v4/index.html @@ -9,7 +9,7 @@ - + Migrate from v3 to v4 - IoT Hub Portal diff --git a/dev/dev-guide/migrations/v4-to-v5/index.html b/dev/dev-guide/migrations/v4-to-v5/index.html index c21fc61b0..52734a51a 100644 --- a/dev/dev-guide/migrations/v4-to-v5/index.html +++ b/dev/dev-guide/migrations/v4-to-v5/index.html @@ -9,7 +9,7 @@ - + Migrate from v4 to v5 - IoT Hub Portal @@ -544,6 +544,11 @@

Azure Connection string The IotDPS Connection String + +Azure__StorageAccount__ConnectionString +Connection string +The Storage Account Connection String + diff --git a/dev/dev-guide/testing/unit-tests-common-practices/index.html b/dev/dev-guide/testing/unit-tests-common-practices/index.html index 9d75b1668..bf222404e 100644 --- a/dev/dev-guide/testing/unit-tests-common-practices/index.html +++ b/dev/dev-guide/testing/unit-tests-common-practices/index.html @@ -9,7 +9,7 @@ - + Unit Tests Common Practices - IoT Hub Portal diff --git a/dev/dev-guide/testing/unit-tests-on-blazor-components/index.html b/dev/dev-guide/testing/unit-tests-on-blazor-components/index.html index 72c8fc734..936e38fdc 100644 --- a/dev/dev-guide/testing/unit-tests-on-blazor-components/index.html +++ b/dev/dev-guide/testing/unit-tests-on-blazor-components/index.html @@ -9,7 +9,7 @@ - + Unit Tests on Blazor components - IoT Hub Portal diff --git a/dev/index.html b/dev/index.html index 271be381d..989d613cc 100644 --- a/dev/index.html +++ b/dev/index.html @@ -8,7 +8,7 @@ - + IoT Hub Portal diff --git a/dev/open-api/index.html b/dev/open-api/index.html index 76466fd17..06377f680 100644 --- a/dev/open-api/index.html +++ b/dev/open-api/index.html @@ -9,7 +9,7 @@ - + Web API Reference - IoT Hub Portal @@ -419,7 +419,7 @@
diff --git a/dev/open-api/swagger-f310b4dc.html b/dev/open-api/swagger-bbaaacc3.html similarity index 98% rename from dev/open-api/swagger-f310b4dc.html rename to dev/open-api/swagger-bbaaacc3.html index 97db9bca2..31414ed83 100644 --- a/dev/open-api/swagger-f310b4dc.html +++ b/dev/open-api/swagger-bbaaacc3.html @@ -58,7 +58,7 @@ } const resize_ob = new ResizeObserver(function(entries) { - parent.update_swagger_ui_iframe_height("f310b4dc"); + parent.update_swagger_ui_iframe_height("bbaaacc3"); }); // start observing for resizing diff --git a/dev/search/search_index.json b/dev/search/search_index.json index 3a17a603b..8815c73d1 100644 --- a/dev/search/search_index.json +++ b/dev/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"IoT Hub Portal","text":"

This project aims to provide a solution for handling IoT Devices easyly. It leverages on Azure IoT Hub or AWS IoT Core for connectivity and device management.

"},{"location":"#features","title":"Features","text":""},{"location":"#quick-start","title":"Quick Start","text":""},{"location":"#azure","title":"Azure","text":"

Quick Start for Azure environment.

"},{"location":"#amazon-web-services","title":"Amazon Web Services","text":"

Quick Start for AWS environment.

"},{"location":"#known-issues-and-limitations","title":"Known Issues and Limitations","text":"

Refer to Known Issues for known issues, gotchas and limitations.

"},{"location":"#support","title":"Support","text":"

This is an open source solution. For bugs and issues with the codebase please log an issue in this repo.

"},{"location":"#credits","title":"Credits","text":""},{"location":"aws/","title":"AWS configurations","text":""},{"location":"aws/#overall-architecture","title":"Overall Architecture","text":"

This schema represent the various components and how they interact to have a better understanding of the various solution elements.

  1. The user is authenticated by the OpenID Connect server.
  2. The user access to the IoT Hub Portal with the OAuth2.0 token.
  3. The IoT Hub portal uses the AWS IoT REST API to retrieve the data.
  4. The IoT Hub portal uses the AWS S3 storage to store the device models configuration (Images, Commands, etc.).
  5. The IoT Hub portal synchronizes its data with the IoT Hub to provide a consistent view of the data.
"},{"location":"aws/#quick-start","title":"Quick Start","text":""},{"location":"aws/#prerequisites","title":"Prerequisites","text":""},{"location":"aws/#deployed-resources","title":"Deployed Resources","text":"

The template will deploy in your AWS Account the Following resources:

"},{"location":"aws/#instructions","title":"Instructions","text":"
  1. Choose a stack name for your AWS Deployment.

  2. Follow next step below to start your deployment:

    1. Press on the button here below to download the template AWS: Download the template

    2. Import your template : From the AWS console: CloudFormation new stack - You can change the deployment region by changing the region directly in the URL - In \"Upload a template file\" import the previously downloaded file With AWS CLI:

      Bash Session
      aws cloudformation deploy --template /path_to_template/awsdeploy.yml --stack-name your-stack-name --region your-region\n

      see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-cli-deploy.html for more option about aws cloudformation deploy

  3. You will get to a page asking you to fill the following fields:

    • Stack Name: A name for the stack where all the template resource would be put into, just choose a meaningful name.
    • PostgreSQL user: The PostgreSQL user name to be used for the IoT Hub Portal database.
    • PostgreSQL password: The PostgreSQL password to be used for the IoT Hub Portal database.
    • AWS Access Key: The AWS Access Key of your AWS environment.
    • AWS Access Secret Key: The AWS Access Secret Key of your AWS environment.
    • Api Client Id: the ID of the API client that will be used to authenticate the portal.
    • Client Id: the ID of the web client that will be used to authenticate the portal.
    • Open Id Authority: The OpenID authority used by the portal.
    • OpenId Metadata URL: The OpenID metadata URL used by the portal.
    • OpenId Scope Name: The Open ID Scope name
"},{"location":"aws/#configurations","title":"Configurations","text":"

This configurations are used to get access to AWS cloud Platform. You have to enter them in a json file to be able to connect to the Iot Hub Portal. Here is a template of a such json file.

JSON
{\n  \"CloudProvider\": \"AWS\",\n  \"AWS:Access\": \"<ACCESS_KEY>\",\n  \"AWS:AccessSecret\": \"<ACCESS_SECRET_KEY>\",\n  \"AWS:Region\": \"<REGION_KEY>\",\n  \"AWS:BucketName\": \"<BUCKET_NAME>\",  \n  \"AWS:AccountId\": \"<ACCOUNT_IDENTIFIER>\",\n  \"AWS:GreengrassRequiredRoles:<ID>\": \"<GREENGRASS_ROLE_NAME>\",\n  \"OIDC:Scope\": \"<SCOPE>\",\n  \"OIDC:MetadataUrl\": \"<METADATA_URL>\",\n  \"OIDC:ClientId\": \"<CLIENT_ID>\",\n  \"OIDC:Authority\": \"<AUTHORITY>\",\n  \"OIDC:ApiClientId\": \"<API_CLIENT_ID>\",\n  \"PostgreSQL:ConnectionString\": \"<POSTGRE_SQL_CONNECTION_STRING>\"\n}\n

Note: You must replace all values in the brackets by your own AWS settings. If you can't find them in the AWS Portal, please contact an administrator of this project to have more information.

You are now ready to start your IoT Hub Portal development !

"},{"location":"azure/","title":"Azure Configurations","text":""},{"location":"azure/#overall-architecture","title":"Overall Architecture","text":"

This schema represent the various components and how they interact to have a better understanding of the various solution elements.

  1. The user is authenticated by the OpenID Connect server.
  2. The user access to the IoT Hub Portal with the OAuth2.0 token.
  3. The IoT Hub portal uses the Azure IoT Hub REST API to retrieve the data.
  4. The IoT Hub portal uses the Azure Device Provisioning Service to manage IoT Edge devices.
  5. The IoT Hub portal uses the Azure Storage account to store the device models configuration (Images, Commands, etc.).
  6. The IoT Hub portal uses the LoRa Key Management Facade to send Cloud to Device (C2D) messages to LoRa devices.
  7. The LoRa Key Management Facade uses Redis to store its cached data.
  8. The LoRa Key Management Facade uses the Azure IoT Hub REST API to retrieve the LoRa device keys and send C2D messages.
  9. The IoT Hub portal synchronizes its data with the IoT Hub to provide a consistent view of the data.

Note: For more information about the LoRa Key Management Facade, see the Azure IoT Edge LoRaWAN Starter Kit page.

"},{"location":"azure/#quick-start","title":"Quick Start","text":""},{"location":"azure/#prerequisites","title":"Prerequisites","text":""},{"location":"azure/#deployed-resources","title":"Deployed Resources","text":"

The template will deploy in your Azure subscription the Following resources:

"},{"location":"azure/#instructions","title":"Instructions","text":"
  1. Choose a solution prefix for your Azure Deployment.

  2. Configure your AD to connect to the portal. Use Portal AD applications configuration page to configure your AD B2C Tenant :

    You should have recorded the following information:

    • OpenID authority: <your-openid-authority>
    • OpenID metadata URL: <your-openid-provider-metadata-url>
    • Client ID: <your-client-id>
    • API Client ID: <your-client-id>
  3. Press on the button here below to start your deployment on Azure:

  4. You will get to a page asking you to fill the following fields:

    • Resource Group: A logical \"folder\" where all the template resource would be put into, just choose a meaningful name.
    • Location: In which DataCenter the resources should be deployed. Make sure to choose a location where IoT Hub is available
    • Unique Solution Prefix: A string that would be used as prefix for all the resources name to ensure their uniqueness.
    • PostgreSQL user: The PostgreSQL user name to be used for the IoT Hub Portal database.
    • PostgreSQL password: The PostgreSQL password to be used for the IoT Hub Portal database.
    • Confirm PostgreSQL password: The PostgreSQL password to be used for the IoT Hub Portal database.
    • Open Id Authority: The OpenID authority used by the portal.
    • OpenId Metadata URL: The OpenID metadata URL used by the portal.
    • Client Id: the ID of the web client that will be used to authenticate the portal.
    • Api Client Id: the ID of the API client that will be used to authenticate the portal.
    • Edge gateway name: the name of your LoRa Gateway node in the IoT Hub.
    • Deploy Device: Do you want demo end devices to be already provisioned (one using OTAA and one using ABP)? If yes set this to true, the code located in the Arduino folder would be ready to use immediately.
    • Reset pin: The reset pin of your gateway (the value should be 7 for the Seed Studio LoRaWan, 25 for the IC880A)
    • Region: In what region are you operating your device (currently only EU868 and US915 is supported)

    see: https://azure.github.io/iotedge-lorawan-starterkit/dev/quickstart/#deployed-azure-infrastructure for more information about the LoRaWan IoT Hub and Azure deployment.

"},{"location":"azure/#configurationssecrets","title":"Configurations/Secrets","text":"

Secrets are used to fill in the login credentials to the cloud platform. You have to enter them in a json file to be able to connect to the IoT Hub Portal. Here is a template of a such json file :

JSON
{\n  \"CloudProvider\": \"Azure\",\n  \"OIDC:Scope\": \"<SCOPE>\",\n  \"OIDC:MetadataUrl\": \"<METADATA_URL>\",\n  \"OIDC:ClientId\": \"<CLIENT_ID>\",\n  \"OIDC:Authority\": \"<AUTHORITY>\",\n  \"OIDC:ApiClientId\": \"<API_CLIENT_ID>\",\n  \"LoRaFeature:Enabled\": \"<TRUE_OR_FALSE>\",\n  \"Azure:LoRaRegionRouterConfig:Url\": \"<LORA_WAN_ROUTER_CONFIGURATION_URL>\",\n  \"Azure:LoRaKeyManagement:Url\": \"<LORA_WAN_KEY_MANAGEMENT_URL>\",\n  \"Azure:LoRaKeyManagement:Code\": \"<LORA_WAN_KEY_MANAGEMENT_CODE>\",\n  \"Kestrel:Certificates:Development:Password\": \"<DEV_PASSWORD>\",\n  \"Azure:IoTHub:ConnectionString\": \"<IOT_HUB_CONNECTION_STRING>\",\n  \"Azure:IoTHub:EventHub:Endpoint\": \"<IOT_HUB_EVENT_HUB_ENDPOINT>\",\n  \"Azure:IoTHub:EventHub:ConsumerGroup\": \"<IOT_HUB_EVENT_HUB_CONSUMER_GROUP>\",\n  \"Azure:IoTDPS:ServiceEndpoint\": \"<SERVICE_END_POINT>\",\n  \"Azure:IoTDPS:LoRaEnrollmentGroup\": \"<LORA_WAN_ENROLLMENT_GROUP>\",\n  \"Azure:IoTDPS:DefaultEnrollmentGroup\": \"<LORA_WAN_DEFAULT_ENROLLMENT_GROUP>\",\n  \"Azure:IoTDPS:ConnectionString\": \"<IOT_DPS_CONNECTION_STRING>\",\n  \"PostgreSQL:ConnectionString\": \"<POSTGRE_SQL_CONNECTION_STRING>\",\n  \"Azure:StorageAccount:ConnectionString\": \"<CONNECTION_STRING_STORAGE_ACCOUNT>\"\n}\n

Note: You must replace all values in the brackets by your own Azure settings. If you can't find them in the Azure Portal, please contact an administrator of this project to have more information.

This json file must be added into your project solution. To do that, click on the AzureIoTHub.Server project in Visual Studio and select Manage User Secrets from the context menu. You can now add your secrets inside this file.

You are now ready to start your IoT Hub Portal development !

"},{"location":"b2c-applications/","title":"Azure AD B2C Tenant with applications","text":"

This solution uses Azure AD B2C to authenticate the portal. In this page you will configure the B2C tenant and two applications (API and Web UI).

By the end, you should have recorded the following information:

Text Only
* OpenID authority: `<your-openid-authority>`\n* OpenID metadata URL: `<your-openid-provider-metadata-url>`\n* Client ID: `<your-client-id>`\n* API Client ID: `<your-client-id>`\n
"},{"location":"b2c-applications/#step-by-step-instructions","title":"Step by Step instructions","text":"
  1. Create an Azure AD B2C Tenant (see: https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant#create-an-azure-ad-b2c-tenant)

    • Record the tenant ID and the tenant name.
  2. After creating your Azure AD B2C Tenant and registering your applications, you need to set up OpenID Connect to secure your applications. Here\u2019s how to find your OpenID authority and OpenID metadata URL:

    1. Determine your OpenID Authority:
      • Your OpenID Authority is the issuer URL of your Azure AD B2C Tenant. It typically follows the format: https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/.
      • Replace <tenant-name> with your actual tenant name.
    2. Find your OpenID Metadata URL:
      • The OpenID Metadata URL for Azure AD B2C tenants is usually in the format: https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=<policy-name>.
      • Replace <tenant-name> and <policy-name> with your actual tenant name and the policy name you are using (like B2C_1_SignUpSignIn).
    3. Make sure to record the OpenID authority and OpenID metadata URL for future configuration steps.
  3. Configure the requiered AD Applications.

    1. Create the IoT Hub Portal API Application:

      • Select App registrations, and then select New registration.
      • Enter a Name for the application. For example, IoT Hub Portal.
      • Under Redirect URI, select Web, and then enter an expected endpoint for your portal (ex: https://tenantName.b2clogin.com/tenantName.onmicrosoft.com/oauth2/authresp)
      • Select Register.
      • Record the Application (client) ID for use in your web API's code.
      • Under Manage, select Certificates & Secrets.
      • Under Client secret, select New client secret.
      • Enter a name for the secret.
      • Record the Client secret for use in your web API's code.
      • Under Manage, select API permissions.
      • Under Configured permissions, select Add a permission.
      • Select the Microsoft APIs tab.
      • Under Commonly used Microsoft APIs, select Microsoft Graph.
      • Select Application permissions.
      • Under Application permissions, expand User, then select:
        • User.Invite.All
        • User.ManageIdentities.All
        • User.Read.All
        • User.ReadWrite.All
      • Select Add permission.
      • If you're prompted to select an account, select your currently signed-in administrator account, or sign in with an account in your Azure AD B2C tenant that's been assigned at least the Cloud application administrator role.
      • Under Manage, select Expose an API.
      • Next to Application ID URI, select the Set link.
      • Under Scopes defined by this API, select Add a scope.
      • Enter the following values to create a scope that defines read access to the API, then select Add scope:
        • Scope name: API.Access (this is the name of the scope that will be used in the template)
        • Admin consent display name: Access to the Portal API
        • Admin consent description: Allows the application to get access to the Portal API
    2. Create the IoT Hub Portal Client Application:

      • Select App registrations, and then select New registration.
      • Enter a Name for the application. For example, IoT Hub Portal Client.
      • Under Redirect URI, select Web, and then enter an expected endpoint for your portal (ex: https://**solutionPrefix**portal.azurewebsites.net/authentication/login-callback)
      • Select Register.
      • Record the Application (client) ID for use in your web client.
      • Select App registrations, and then select the web application that should have access to the API.
      • Under Manage, select API permissions.
      • Under Configured permissions, select Add a permission.
      • Select the My APIs tab.
      • Select the API to which the web application should be granted access.
      • Under Permission, expand API, and then select the scope that you defined earlier.
      • Select Add permissions.
      • Select Grant admin consent for (your tenant name).
      • If you're prompted to select an account, select your currently signed-in administrator account, or sign in with an account in your Azure AD B2C tenant that's been assigned at least the Cloud application administrator role.
      • Select Yes.
      • Select Refresh, and then verify that \"Granted for ...\" appears under Status for both scopes.
  4. Configure the required User flow:

    1. Select User flows, and then select New user flow.
    2. Under Select a user flow type, select Sign in, then select Create.
    3. Enter a name for the flow SignIn, then select Create.
"},{"location":"concepts/","title":"Core Concepts","text":"

The Azure IoT Hub portal inherits from Azure IoT Hub concepts to manage IoT devices.

It relies on the following concepts:

"},{"location":"concepts/#device-models","title":"Device models","text":"

By using this capability, the application can create logical representations of IoT devices. This feature is designed to configure a set of sharable properties between devices. When creating a device, the user is asked to specify the device model. The application will then apply the properties of the device model to the device.

"},{"location":"concepts/#parameters","title":"Parameters","text":""},{"location":"concepts/#built-in-models","title":"Built-in models","text":"

Built-in models are predefined device models that can be used by the application. This functionality is exactly the same as standard device models except that the properties are not editable and the device model is not removable via the Portal.

Note: Creating and updating built-in models is not available in the portal. They can be managed using the Azure IoT Hub portal APIs.

See Device Model API reference for more information.

"},{"location":"concepts/#devices","title":"Devices","text":"

Devices are the physical IoT devices that are provisioned by the application. They are represented by an object that is stored in the Azure IoT Hub as the device twin.

"},{"location":"concepts/#device-parameters","title":"Device Parameters","text":""},{"location":"concepts/#device-twin-tags","title":"Device Twin tags","text":"

To store additional information about the device, the application uses device twin tags.

Name Position Description deviceName tags.deviceName Field that contains the device friendly name.note: if not set, the portal will show the device id instead of the device name until it's configured. modelId tags.modelId Field that contains the device model identifier that the device is related.note: if not set, the device is not usable on the IoT hub portal. supportLoRaFeatures tags.supportLoRaFeatures Field that specifies if the device must support LoRa features.note: if not set, LoRa features will be available on the device."},{"location":"concepts/#iot-edge","title":"IoT Edge","text":"

IoT Edge is fully herited from Azure IoT Hub concepts. In the portal, the user can mangage the IoT Edge devices stored in Azure IoT Hub. For more information about Azure IoT Edge, see Azure IoT Edge documentation.

"},{"location":"concepts/#iot-edge-parameters","title":"IoT Edge Parameters","text":""},{"location":"concepts/#last-deployment","title":"Last deployment","text":"

The last deployment section shows information about the deployment manifest that is currently applied to the IoT Edge device.

"},{"location":"concepts/#iot-edge-module","title":"IoT Edge Module","text":"

The IoT Edge module section represents the modules that are currently deployed on the IoT Edge device. It doesn't include the system modules of IoT Edge (edgeAgent and edgeHub). With the portal, the user can interact with these modules and manage them (Get last module logs, restart module, etc.).

"},{"location":"concepts/#iot-edge-device-twin-tags","title":"IoT Edge Device Twin tags","text":"

To store additional information about the device, the application uses device twin tags.

Name Position Description Environment tags.env Field that contains the Device Environment value.note: this tag may be used to target deployment manifests for the IoT Edge Type tags.type Field that contains The type of the IoT Edge device.note: this tag may be used to target deployment manifests for the IoT Edge"},{"location":"concepts/#dps-enrollment-groups","title":"DPS Enrollment groups","text":"

The IoT Hub portal relies on Azure Device Provisioning Enrollement groups to manage IoT Edge device connection strings. When clicking on \"Connect\" in the IoT Edge details page, the user can access the device unique credentials in the enrollment group.

Note: see Provision the device with its cloud identity to know how to configure the IoT Edge to use these credentials to connect to the platform.

"},{"location":"concepts/#device-configuration","title":"Device Configuration","text":"

By using the portal, users can manage the device configuration and deploy to devices that are targeted by the configuration.

It relies on the Device Model to define the configuration parameters that can be deployed to the devices.

"},{"location":"concepts/#device-configuration-template","title":"Device Configuration template","text":"

Under the cover, the configuration is stored in the Azure IoT Hub as the twin configuration.

JSON
{\n    \"id\": \"<configuration-name>-<timestamp>\",\n    \"schemaVersion\": \"1.0\",\n    \"labels\": {\n        \"created-by\": \"Azure IoT hub Portal\",\n        \"configuration-id\": \"<configuration-name>\"\n    },\n    \"content\": {\n        \"deviceContent\": {\n            \"properties.desired.**\": **\n        }\n    },\n    \"targetCondition\": \"tags.modelId = '<The model identifier>' and tags.** = '**' AND ...\",\n    \"createdTimeUtc\": \"2022-06-13T07:32:19.7376998Z\",\n    \"lastUpdatedTimeUtc\": \"2022-06-13T07:32:19.7376998Z\",\n    \"priority\": 100\n}\n

Please note that the created-by label is used to identify the configuration created by the IoT Hub portal.

"},{"location":"concepts/#iot-edge-configuration","title":"IoT Edge Configuration","text":"

The IoT Edge configuration concerns the IoT Edge deployment manifests that are currently present in the IoT Hub. The portal can be used to see the details of the configurations.

Note: At this time the portal cannot be used to update the configurations.

"},{"location":"concepts/#target","title":"Target","text":"

The parameters are related to the IoT Edge deployment manifest target condition field. The IoT Hub portal will use the Target condition to extract this values from the deployment manifest.

Name Position Description Owner tags.owner Owner tag filter condition from the Deployment Manifest. Environment tags.env Environment tag filter from the Deployment Manifest. Type tags.type Device type tag filter from the Deployment Manifest. Expected value for IoT Edge LoRaWAN LNS is LoRa Network Server, otherwise the value must be Other"},{"location":"concepts/#enrollment-groups","title":"Enrollment groups","text":"

The IoT Hub portal relies on Azure Device Provisioning Enrollment groups to manage IoT device connection credentials.

For each device model, the portal will create a new enrollment group with symmetric key attestation. By clicking on \"Connect\" in the device details page, the portal will show unique credentials to the device for the corresponding enrollment group.

Furthermore, the enrollment group is configured to provide initial device twin state:

JSON
{\n  \"tags\": {\n    \"modelId\": \"......\"\n  },\n  \"properties\": {\n    \"desired\": {}\n  }\n}\n

For more information, see Azure Device Provisioning Enrollement groups.

"},{"location":"concepts/#lorawan","title":"LoRaWAN","text":"

LoRaWAN features are activated by default, providing a way to configure IoT Devices that supports LoRaWAN connectivity in the Portal. Internally, the LoRaWAN connectivity is expected to be provided by IoTEdge LoRaWAN StarterKit. The IoT Hub portal will manage devices by modifying their twin properties to make them working with this solution.

Note: to disable LoRa Features, change the value of LoRaFeature__Enabled to false in the Portal App Settings.

"},{"location":"concepts/#lorawan-device-models","title":"LoRaWAN Device Models","text":"

For regular Device Models the IoT hub portal provides the possibility to manage LoRaWAN device models. To activate the LoRaWAN features on the device model, the user have to enable the option in the LoRa Device section

Note: once activated, the device model detail adds a new tab called \"LORAWAN\" that adds new settings to the device model.

"},{"location":"concepts/#lorawan-device-model-parameters","title":"LoRaWAN Device Model Parameters","text":"

The parameters for the device models are parameters that are stored in the IoT Hub portal and retrieved for devices that inherits from this device model.

Note: When changing the value of a parameter, the device will be updated with the new value. In that case, user should then modify each device and re-save it to get the correct properties.

Note: for more information about LoRaWAN properties, please refer to the LoRaWAN StarterKit Documentation

"},{"location":"concepts/#commands","title":"Commands","text":"

The devices commands are pre-stored frames that the user can add to the device model and then will be able to use on the device detail page to launch to the device.

"},{"location":"concepts/#lorawan-devices","title":"LoRaWAN Devices","text":"

LoRaWAN devices are accessible from the IoT Hub portal for devices that inherits from the LoRaWAN device model. The LoRaWAN tab shows the device details.

Note: By selecting the correct device model on the first tab, the portal will automatically take LoRaWAN settings from the device model to apply on the device.

"},{"location":"concepts/#concentrators","title":"Concentrators","text":""},{"location":"concepts/#concentrator-parameters","title":"Concentrator Parameters","text":""},{"location":"concepts/#concentrator-tags","title":"Concentrator Tags","text":"

To store additional information about the concentrator, the application will use the target device to extract values:

Name Position Description Device Name tags.deviceName Field that contains the Device name. Region tags.loraRegion Field that contains the Device region. deviceType tags.deviceType Field that contains The type of device. Expected value is LoRa Concentrator"},{"location":"concepts/#command-execution","title":"Command Execution","text":"

To execute the command, the device should have joined the network. The message below explains that the device have to be connected to the network ant commands are disabled until the device is connected to the network.

"},{"location":"concepts/#command-execution-flow","title":"Command execution flow","text":"

The schema below explain how the command execution flow works.

sequenceDiagram User->>+IoT Hub Portal: Send Command (DeviceId, FrameId) IoT Hub Portal->>+LoRa Key Management Facade: POST /api/cloudtodevicemessage LoRa Key Management Facade->>+Azure IoT Hub: Invoke Device Method to Network Server Azure IoT Hub-->>-LoRa Key Management Facade: Cloud To Device Method Result LoRa Key Management Facade-->>-IoT Hub Portal: Send Cloud To Device Message Result IoT Hub Portal-->>-User: Command send result

See https://azure.github.io/iotedge-lorawan-starterkit/2.0.0/quickstart/#cloud-to-device-message for more information about the Cloud To Device Message involed in the LoRa WAN device commands execution flow.

"},{"location":"concepts/#automatic-device-configuration-for-lora-wan","title":"Automatic device configuration for LoRa WAN","text":"

When modifying the device model, the IoT Hub portal will automatically create a new Device Configuration that will target the IoT devices that have the corresponding modelId tag.

The IoT Hub portal will create a new Rollout deployment that will remove older configuration and add the new configuration.

This process ensure that the devices twin will be updated at scale by the IoT hub and each devices that inherit for the model will be updated according the model configuration.

The configuration will be created with the following schema:

JSON
{\n    \"id\": \"<model-name>-<timestamp>\",\n    \"schemaVersion\": \"1.0\",\n    \"labels\": {\n        \"created-by\": \"Azure IoT hub Portal\"\n    },\n    \"content\": {\n        \"deviceContent\": {\n            \"properties.desired.AppEUI\": \"<The Device Model OTAA AppEUI>\",\n            \"properties.desired.SensorDecoder\": \"<The Device Model Sensor Decoder>\"\n        }\n    },\n    \"targetCondition\": \"tags.modelId = '<The model identifier>'\",\n    \"createdTimeUtc\": \"2022-02-28T20:29:39.128Z\",\n    \"lastUpdatedTimeUtc\": \"2022-02-28T20:29:39.128Z\",\n    \"priority\": 0\n}\n

Please note that the created-by label is used to identify the configuration created by the IoT Hub portal.

For more information see Automatic IoT device and module management.

"},{"location":"dev-guide/","title":"Developer Guide","text":""},{"location":"dev-guide/#directory-structure","title":"Directory Structure","text":"

The code is organized into the following directory structure:

"},{"location":"dev-guide/#overall-architecture","title":"Overall Architecture","text":"

This schema represent the various components and how they interact to have a better understanding of the various solution elements.

  1. The user is authenticated by the OpenID Connect server.
  2. The user access to the IoT Hub Portal with the OAuth2.0 token.
  3. The IoT Hub portal uses the Azure IoT Hub REST API to retrieve the data.
  4. The IoT Hub portal uses the Azure Device Provisioning Service to manage IoT Edge devices.
  5. The IoT Hub portal uses the Azure Storage account to store the device models configuration (Images, Commands, etc.).
  6. The IoT Hub portal uses the LoRa Key Management Facade to send Cloud to Device (C2D) messages to LoRa devices.
  7. The LoRa Key Management Facade uses Redis to store its cached data.
  8. The LoRa Key Management Facade uses the Azure IoT Hub REST API to retrieve the LoRa device keys and send C2D messages.
  9. The IoT Hub portal synchronizes its data with the IoT Hub to provide a consistent view of the data.

Note: For more information about the LoRa Key Management Facade, see the Azure IoT Edge LoRaWAN Starter Kit page.

"},{"location":"dev-guide/#prerequisites","title":"Prerequisites","text":"

The following should be completed before proceeding with the IoT Hub Portal development or deployment in your environment.

Before getting started, it is better to master the tools below:

Once you know the basics of these technologies and tools, you must follow these last steps to set up your working environment.

Once you have download Docker, you must install the WSL 2 Linux kernel. To do that, please refer to the official Microsoft documentation. You can choose the linux distribution of your choice, for example Ubuntu.

"},{"location":"dev-guide/#secrets","title":"Secrets","text":"

Secrets are used to fill in the login credentials to the cloud platform. You have to enter them in a json file to be able to connect to the IoT Hub Portal. Here is a template of a such json file :

JSON
{\n  \"StorageAccount:ConnectionString\": \"<CONNECTION_STRING_STORAGE_ACCOUNT>\",\n  \"StorageAccount:BlobContainerName\": \"<BLOB_CONTAINER_NAME>\",\n  \"OIDC:Scope\": \"<SCOPE>\",\n  \"OIDC:MetadataUrl\": \"<METADATA_URL>\",\n  \"OIDC:ClientId\": \"<CLIENT_ID>\",\n  \"OIDC:Authority\": \"<AUTHORITY>\",\n  \"OIDC:ApiClientId\": \"<API_CLIENT_ID>\",\n  \"LoRaRegionRouterConfig:Url\": \"<LORA_WAN_ROUTER_CONFIGURATION_URL>\",\n  \"LoRaKeyManagement:Url\": \"<LORA_WAN_KEY_MANAGEMENT_URL>\",\n  \"LoRaKeyManagement:Code\": \"<LORA_WAN_KEY_MANAGEMENT_CODE>\",\n  \"LoRaFeature:Enabled\": \"<TRUE_OR_FALSE>\",\n  \"Kestrel:Certificates:Development:Password\": \"<DEV_PASSWORD>\",\n  \"IoTHub:ConnectionString\": \"<IOT_HUB_CONNECTION_STRING>\",\n  \"IoTHub:EventHub:Endpoint\": \"<IOT_HUB_EVENT_HUB_ENDPOINT>\",\n  \"IoTHub:EventHub:ConsumerGroup\": \"<IOT_HUB_EVENT_HUB_CONSUMER_GROUP>\",\n  \"IoTDPS:ServiceEndpoint\": \"<SERVICE_END_POINT>\",\n  \"IoTDPS:LoRaEnrollmentGroup\": \"<LORA_WAN_ENROLLMENT_GROUP>\",\n  \"IoTDPS:DefaultEnrollmentGroup\": \"<LORA_WAN_DEFAULT_ENROLLMENT_GROUP>\",\n  \"IoTDPS:ConnectionString\": \"<IOT_DPS_CONNECTION_STRING>\",\n  \"PostgreSQL:ConnectionString\": \"<POSTGRE_SQL_CONNECTION_STRING>\"\n}\n

Note: You must replace all values in the brackets by your own Azure settings. If you can't find them in the Azure Portal, please contact an administrator of this project to have more information.

This json file must be added into your project solution. To do that, click on the AzureIoTHub.Server project in Visual Studio and select Manage User Secrets from the context menu. You can now add your secrets inside this file.

You are now ready to start your IoT Hub Portal development !

"},{"location":"dev-guide/#iot-hub-portal-configuration","title":"IoT Hub Portal Configuration","text":"

By deploying the IoT Hub Portal, the user can configure the IoT Hub and the LoRaWAN network.

Since the IoT Hub Portal is deployed as a Docker container, the application settings can be configured with environment variables.

"},{"location":"dev-guide/#application-settings","title":"Application settings","text":"

Here are different settings that the user can configure:

"},{"location":"dev-guide/#connection-strings","title":"Connection strings","text":"

Here are different connection strings that the user can configure:

Note: For a production environment, an Azure Key Vault is advised to store the connection strings.

"},{"location":"dev-guide/#optional-security-settings","title":"Optional Security Settings","text":"

There are several optional security settings that the user can configure. These settings are not required for the Portal to work. By default the Portal is configured to set security levels to Microsoft.IdentityModel.Tokens defaults but the user can override these settings.

"},{"location":"dev-guide/#device-tags","title":"Device tags","text":"

The IoT Hub portal uses some tags to configure the devices. The tags are stored in the Azure IoT Hub in Device Twins.

"},{"location":"dev-guide/#storage-account","title":"Storage Account","text":"

The Storage Account is used to store the device models images. You can use the same Storage Account that is used by the LoRa Key Management Facade. This solution will use tables and blob storage to store its data. There is no need to create the containers, the application will do it for you.

"},{"location":"dev-guide/#blob-storage","title":"Blob Storage","text":"

The application uses the following blob storage:

"},{"location":"dev-guide/#working-with-the-documentation","title":"Working with the documentation","text":"

This documentation site is build using Material for MkDocs and Mike.

docs/main is a detached branch that is locked and only accepts PRs. On PR merge, Github Pages will automatically update the documentation website.

"},{"location":"dev-guide/#how-to-update-the-documentation","title":"How to update the documentation","text":"
  1. Checkout the branch that contains the documentation:

    Bash Session
    git checkout origin/docs/main\ngit checkout -b docs/<your_branch_name> \n
  2. Install dependencies

    Bash Session
    pip install -r requirements.txt\n
  3. Previewing as you write

    Bash Session
    mkdocs serve\n
  4. PRs are gated by a markdownlint check. You should use markdownlint to lint any new changes on documentation. For example you can use the vs code extension https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint

  5. Update the documentation
  6. Commit your changes
  7. Push your changes to the branch
  8. Create a PR
"},{"location":"dev-guide/#customization","title":"Customization","text":"

Refer to Material for MkDocs documentations:

"},{"location":"dev-guide/#versioning","title":"Versioning","text":"

Mike is used to generate automatically a new documentation version when a release has been published, using ci/cd pipelines.

For manual workflows (e.g. delete or retitle an existing version), please refer to Mike documentation

"},{"location":"dev-guide/#problem-details","title":"Problem Details","text":"

On IoT Hub Portal, we use the library Hellang.Middleware.ProblemDetails which implements RFC7807 to describe issues/problems that occurred on backend.

"},{"location":"dev-guide/#handle-a-new-exception-using-problem-details","title":"Handle a new exception using Problem Details","text":"

\ud83d\udca1 You can also map exceptions from dotnet framework and third parties.

"},{"location":"dev-guide/#handle-problem-details-exceptions-on-frontend","title":"Handle Problem Details exceptions on frontend","text":"

On frontend, http client uses a delegating handler ProblemDetailsHandler to:

On Blazor views, http calls must be catched to capture any exceptions of type ProblemDetailsException to be able to execute any business code to process them.

When an http call fails, the user must be notified visually by the application: A component Error has been made to respond to this use case. Below an example on how to:

C#
@code {\n    // Inject the reference to the Error component as a cascading parameter\n    [CascadingParameter]\n    public Error Error {get; set;}\n\n    private await Task GetData()\n    {\n        try\n        {\n            // Execute an http request\n        }\n        catch (ProblemDetailsException exception)\n        {\n            // Pass the ProblemDetailsException exception to Error component using its method ProcessProblemDetails()\n            // The Error component will alert the user by showing a (snackbar/dialog) using the content of the exception\n            Error?.ProcessProblemDetails(exception)\n        }\n    }\n}\n
"},{"location":"dev-guide/#how-to-install-entity-framework-core","title":"How to install Entity Framework Core","text":"

Follow the next step to install EF Core:

  1. Open the terminal and run this command:

    Bash Session
    dotnet tool install --global dotnet-ef\n
"},{"location":"dev-guide/#how-to-create-entityframework-migrations-for-postgresql-and-mysql","title":"How to create EntityFramework migrations for PostgreSQL and MySQL","text":"

For the project need, we need two database providers which are PostgreSQL and MySQL, which led us to review the architecture set up for the EntityFramework migrations. Here is a diagram showing the two architectures.

C4Deployment\n  title Architecture for multiple providers\n\n  Deployment_Node(provider1, \"Provider1\", \"Provider1\"){\n    Container(provider1dbcontextfactory, \"Provider1DbContextFactory\", \"File\", \"\")\n    Container(provider1migrations, \"Provider1Migrations\", \"Folder\", \"\")\n  }\n\n  Deployment_Node(provider2, \"Provider2\", \"Provider2\"){\n    Container(provider2dbcontextfactory, \"Provider2DbContextFactory\", \"File\", \"\")\n    Container(provider2migrations, \"Provider2Migrations\", \"Folder\", \"\")\n  }\n\n  Deployment_Node(dal, \"Infrastructure Layer\", \"Dal\"){\n    Container(dbcontext, \"DbContext\", File, \"\")\n  }\n\n  Rel(provider1dbcontextfactory, dbcontext, \"dependency\", \"\")\n  Rel(provider2dbcontextfactory, dbcontext, \"dependency\", \"\")

Follow the next steps to create EF migration:

  1. Go into the Server project folder with terminal

    Bash Session
    cd .\\IoTHub.Portal.Server\\\n
  2. Execute this command for PostgreSQL provider

    Bash Session
    dotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.Postgres\\ -v -- --DbProvider PostgreSQL\n
  3. Execute this command for MySQL provider

    Bash Session
    dotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.MySql\\ -v -- --DbProvider MySQL\n
  4. Open the created migration and follow the following steps:

    1. Move the using directive into the namespace directive

    2. Add \"_ =\" before each statement of the Up and Down methods

    3. Add the CGI copyright to the top of the file

"},{"location":"open-api/","title":"Web API Reference","text":""},{"location":"about/credits/","title":"Credits","text":""},{"location":"about/issues/","title":"Known Issues and Limitations","text":"

Refer to Known Issues for known issues, gotchas and limitations.

"},{"location":"about/license/","title":"MIT License","text":"

Copyright \u00a9 2021 CGI France

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.

"},{"location":"about/support/","title":"Support","text":"

This is an open source solution. For bugs and issues with the codebase please log an issue in this repo.

"},{"location":"dev-guide/conception/diagrams/","title":"Diagrams","text":"

In order to better understand the needs of the project, here is a use case diagram regrouping the current use cases of the project.

"},{"location":"dev-guide/conception/diagrams/#connected-objects","title":"Connected objects","text":"
graph LR\n    A[End user] --> B(Display the list of connected objects)\n    A --> C(Add a connected object)\n    C -->|Extend| B\n    D(Import a list of connected objects) -->|Extend| B\n    E(Download a model) -->|Extend| B\n    F(Export the list of connected objects) -->|Extend| B\n    G(Delete a connected object) -->|Extend| B\n    H(Go to the details of a connected object) -->|Extend| B\n    I(Search for connected objects) -->|Extend| B
"},{"location":"dev-guide/conception/diagrams/#connected-object-models","title":"Connected object models","text":"
graph LR\n    A[End user] --> J(Display the list of connected object models)\n    A --> K(Add a connected object model)\n    K -->|Extend| J\n    L(Delete a connected object model) -->|Extend| J\n    M(Go to the details of a connected object model) -->|Extend| J
"},{"location":"dev-guide/conception/diagrams/#connected-object-configurations","title":"Connected object configurations","text":"
graph LR\n    A[End user] --> N(Display the list of connected object configurations)\n    A --> O(Add a connected object configuration)\n    O -->|Extend| N\n    P(Go to the details of a connected object configuration) --> |Extend| N
"},{"location":"dev-guide/conception/diagrams/#edge-connected-object-models","title":"Edge connected object models","text":"
graph LR\n    A[End user] --> Q(Display the list of Edge connected object models)\n    R(Add an Edge connected object model) -->|Extend| Q\n    S(Delete an Edge connected object model) -->|Extend| Q\n    T(Go to the details of a model of Edge connected object) -->|Extend| Q\n    U(Search for Edge connected object models) -->|Extend| Q
"},{"location":"dev-guide/conception/diagrams/#edge-connected-objects","title":"Edge connected objects","text":"
graph LR\n    A[End user] --> V(Display the list of Edge connected objects)\n    W(Add an Edge connected object) -->|Extend| V\n    X(Delete an Edge connected object) -->|Extend| V\n    Y(Go to the details of a connected object Edge) -->|Extend| V\n    Z(Search for Edge connected objects) -->|Extend| V
"},{"location":"dev-guide/conception/diagrams/#concentrators","title":"Concentrators","text":"
graph LR\n    A[End user] --> AA(Display the list of concentrators)\n    AB(Add a concentrator) -->|Extend| AA\n    AC(Delete a concentrator) -->|Extend| AA\n    AD(Go to the details of a concentrator) -->|Extend| AA
"},{"location":"dev-guide/conception/diagrams/#tags","title":"Tags","text":"
graph LR\n    A[End user] --> AE(Display the list of tags)\n    AF(Add a tag) -->|Extend| AE\n    AG(Delete a tag) -->|Extend| AE

Now, here is a diagram representing the multilayer technical architecture of the project.

C4Deployment\n    title Multilayer technical architecture\n\n    Deployment_Node(api, \"Server\", \"API\"){\n        Container(controllers, \"Controllers\", \"C#\", \"They are used to route HTTP requests, they call the methods of the services and they return the content of the HTTP response as well as a HTTP code.\")\n        Container(services, \"Services\", \"C#\", \"They are used to define the business logic as to call the methods of the data access layer for example.\")\n    }\n\n    Deployment_Node(bll, \"Application\", \"BLL\"){\n        Container(iservices, \"Services\", C#, \"This package represents the interfaces of the services.\")\n    }\n\n    Deployment_Node(dal, \"Infrastructure\", \"DAL\"){\n        Deployment_Node(uow, \"UnitOfWork\", \"UOW\"){\n            Container(repositories, \"Repositories\", \"C# and EntityFramework\", \"A repository represents all the data management methods of an entity of the project.\")\n        }\n    }\n\n    Deployment_Node(domain, \"Domain\", \"Domain\"){\n        Container(entities, \"Entities\", \"C#\", \"They are used as object representation of tables in a database.\")\n        Container(irepositories, \"Repositories\", \"C#\", \"This package represents the interfaces of the repositories.\")\n    }\n\n    Rel(iservices, services, \"dependency\", \"\")\n    Rel(repositories, services, \"dependency\", \"\")\n    Rel(entities, services, \"dependency\", \"\")\n    Rel(entities, iservices, \"dependency\", \"\")\n    Rel(iservices, repositories, \"dependency\", \"\")\n    Rel(entities, repositories, \"dependency\", \"\")

Now, to better understand the technical architecture of the project, here is a class diagram representing it.

classDiagram\n    direction LR\n    class AdminController{\n        -String value\n    }\n    class DashboardController{\n        -String value\n    }\n    class DeviceConfigurationsController{\n        -String value\n    }\n    class DeviceModelControllerBase{\n        -String value\n    }\n    class DeviceModelPropertiesController{\n        -String value\n    }\n    class DeviceModelPropertiesControllerBase{\n        -String value\n    }\n    class DeviceModelController{\n        -String value\n    }\n    class DevicesController{\n        -String value\n    }\n    class DevicesControllerBase{\n        -String value\n    }\n    class DeviceTagSettingsController{\n        -String value\n    }\n    class EdgeDevicesController{\n        -String value\n    }\n    class EdgeModelsController{\n        -String value\n    }\n    class IdeasController{\n        -String value\n    }\n    class SettingsController{\n        -String value\n    }\n    class LoRaWANCommandsController{\n        -String value\n    }\n    class LoRaWANConcentratorsController{\n        -String value\n    }\n    class LoRaWANDeviceModelsController{\n        -String value\n    }\n    class LoRaWANDevicesController{\n        -String value\n    }\n    class LoRaWANFrequencyPlansController{\n        -String value\n    }\n    LoRaWANDeviceModelsController --|> DeviceModelsControllerBase\n    LoRaWANDevicesController --|> DevicesControllerBase\n    DeviceModelPropertiesController --|> DeviceModelPropertiesControllerBase\n    DeviceModelsController --|> DeviceModelsControllerBase\n    DevicesController --|> DevicesControllerBase\n    class ConfigService{\n        -String value\n    }\n    class DeviceConfigurationsService{\n        -String value\n    }\n    class DeviceModelPropertiesService{\n        -String value\n    }\n    class DeviceModelService{\n        -String value\n    }\n    class DevicePropertyService{\n        -String value\n    }\n    class DeviceService{\n        -String value\n    }\n    class DeviceServiceBase{\n        -String value\n    }\n    class DeviceTagService{\n        -String value\n    }\n    class EdgeDevicesService{\n        -String value\n    }\n    class EdgeModelService{\n        -String value\n    }\n    class ExternalDeviceService{\n        -String value\n    }\n    class IdeaService{\n        -String value\n    }\n    class LoRaWANCommandService{\n        -String value\n    }\n    class LoRaWANConcentratorService{\n        -String value\n    }\n    class LoRaWanDeviceService{\n        -String value\n    }\n    class SubmitIdeaRequest{\n        -String value\n    }\n    DeviceService --|> DeviceServiceBase\n    LoRaWanDeviceService --|> DeviceServiceBase\n    class IConfigService\n    <<interface>> IConfigService\n    class IDeviceConfigurationsService\n    <<interface>> IDeviceConfigurationsService\n    class IDeviceModelPropertiesService\n    <<interface>> IDeviceModelPropertiesService\n    class IDeviceModelService\n    <<interface>> IDeviceModelService\n    class IDevicePropertyService\n    <<interface>> IDevicePropertyService\n    class IDeviceService\n    <<interface>> IDeviceService\n    class IDeviceTagService\n    <<interface>> IDeviceTagService\n    class IEdgeDevicesService\n    <<interface>> IEdgeDevicesService\n    class IEdgeModelService\n    <<interface>> IEdgeModelService\n    class IExternalDeviceService\n    <<interface>> IExternalDeviceService\n    class IIdeaService\n    <<interface>> IIdeaService\n    class ILoRaWANCommandService\n    <<interface>> ILoRaWANCommandService\n    class ILoRaWANConcentratorService\n    <<interface>> ILoRaWANConcentratorService\n    class ILoRaWanManagementService\n    <<interface>> ILoRaWanManagementService\n    ConfigService ..|> IConfigService\n    DeviceConfigurationsService ..|> IDeviceConfigurationsService\n    DeviceModelPropertiesService ..|> IDeviceModelPropertiesService\n    DeviceModelService ..|> IDeviceModelService\n    DevicePropertyService ..|> IDevicePropertyService\n    DeviceServiceBase ..|> IDeviceService\n    DeviceTagService ..|> IDeviceTagService\n    EdgeDevicesService ..|> IEdgeDevicesService\n    EdgeModelService ..|> IEdgeModelService\n    ExternalDeviceService ..|> IExternalDeviceService\n    IdeaService ..|> IIdeaService\n    LoRaWANCommandService ..|> ILoRaWANCommandService\n    LoRaWANConcentratorService ..|> ILoRaWANConcentratorService\n    class ConcentratorRepository{\n        -String value\n    }\n    class DeviceModelCommandRepository{\n        -String value\n    }\n    class DeviceModelPropertiesRepository{\n        -String value\n    }\n    class DeviceModelRepository{\n        -String value\n    }\n    class DeviceRepository{\n        -String value\n    }\n    class DeviceTagRepository{\n        -String value\n    }\n    class DeviceTagValueRepository{\n        -String value\n    }\n    class EdgeDeviceModelCommandRepository{\n        -String value\n    }\n    class EdgeDeviceModelRepository{\n        -String value\n    }\n    class EdgeDeviceRepository{\n        -String value\n    }\n    class GenericRepository{\n        -String value\n    }\n    class LabelRepository{\n        -String value\n    }\n    class LoRaDeviceTelemetryRepository{\n        -String value\n    }\n    class LorawanDeviceRepository{\n        -String value\n    }\n    class UnitOfWork{\n        -String value\n    }\n    class IConcentratorRepository\n    <<interface>> IConcentratorRepository\n    class IDeviceModelCommandRepository\n    <<interface>> IDeviceModelCommandRepository\n    class IDeviceModelPropertiesRepository\n    <<interface>> IDeviceModelPropertiesRepository\n    class IDeviceModelRepository\n    <<interface>> IDeviceModelRepository\n    class IDeviceRepository\n    <<interface>> IDeviceRepository\n    class IDeviceTagRepository\n    <<interface>> IDeviceTagRepository\n    class IDeviceTagValueRepository\n    <<interface>> IDeviceTagValueRepository\n    class IEdgeDeviceModelCommandRepository\n    <<interface>> IEdgeDeviceModelCommandRepository\n    class IEdgeDeviceModelRepository\n    <<interface>> IEdgeDeviceModelRepository\n    class IEdgeDeviceRepository\n    <<interface>> IEdgeDeviceRepository\n    class ILabelRepository\n    <<interface>> ILabelRepository\n    class ILoRaDeviceTelemetryRepository\n    <<interface>> ILoRaDeviceTelemetryRepository\n    class ILorawanDeviceRepository\n    <<interface>> ILorawanDeviceRepository\n    class IRepository\n    <<interface>> IRepository\n    class IUnitOfWork\n    <<interface>> IUnitOfWork\n    UnitOfWork ..|> IUnitOfWork\n    ConcentratorRepository ..|> IConcentratorRepository\n    ConcentratorRepository --|> GenericRepository\n    DeviceModelCommandRepository ..|> IDeviceModelCommandRepository\n    DeviceModelCommandRepository --|> GenericRepository\n    DeviceModelPropertiesRepository ..|> IDeviceModelPropertiesRepository\n    DeviceModelPropertiesRepository --|> GenericRepository\n    DeviceModelRepository ..|> IDeviceModelRepository\n    DeviceModelRepository --|> GenericRepository\n    DeviceRepository ..|> IDeviceRepository\n    DeviceRepository --|> GenericRepository\n    DeviceTagRepository ..|> IDeviceTagRepository\n    DeviceTagRepository --|> GenericRepository\n    DeviceTagValueRepository ..|> IDeviceTagValueRepository\n    DeviceTagValueRepository --|> GenericRepository\n    EdgeDeviceModelCommandRepository ..|> IEdgeDeviceModelCommandRepository\n    EdgeDeviceModelCommandRepository --|> GenericRepository\n    EdgeDeviceModelRepository ..|> IEdgeDeviceModelRepository\n    EdgeDeviceModelRepository --|> GenericRepository\n    EdgeDeviceRepository ..|> IEdgeDeviceRepository\n    EdgeDeviceRepository --|> GenericRepository\n    GenericRepository ..|> IRepository\n    LabelRepository ..|> ILabelRepository\n    LabelRepository --|> GenericRepository\n    LoRaDeviceTelemetryRepository ..|> ILoRaDeviceTelemetryRepository\n    LoRaDeviceTelemetryRepository --|> GenericRepository\n    LorawanDeviceRepository ..|> ILorawanDeviceRepository\n    LorawanDeviceRepository --|> GenericRepository
"},{"location":"dev-guide/migrations/v3-to-v4/","title":"Migrate from v3 to v4","text":"

To migrate from v3 to v4 manually, you have to add two new settings to the portal web app. These two settings are required to to pull devices telemetry from the IoT Hub:

Name Setting Type Detail IoTHub__EventHub__ConsumerGroup Application setting (Default value iothub-portal) The name of the consumer group used to to pull data from the IoT Hub IoTHub__EventHub__Endpoint Connection string The IotHub Event Hub compatible endpoint

Below the required steps for each settings:

"},{"location":"dev-guide/migrations/v3-to-v4/#iothub__eventhub__consumergroup","title":"IoTHub__EventHub__ConsumerGroup","text":"
  1. Go to your IoT Hub
  2. Navigate to menu Built-in endpoints
  3. Create a consumer group with the name iothub-portal
  4. Back to the portal web app, add a new application setting with name IoTHub__EventHub__ConsumerGroup and with value iothub-portal
"},{"location":"dev-guide/migrations/v3-to-v4/#iothub__eventhub__endpoint","title":"IoTHub__EventHub__Endpoint","text":"
  1. Go to your IoT Hub
  2. Navigate to menu Built-in endpoints
  3. On the section Event Hub compatible endpoint
    1. Select the shared access policy service
    2. Copy the value of the event Hub-compatible endpoint
  4. Back to the portal web app, add a new connection setting with name IoTHub__EventHub__Endpoint and with value the event Hub-compatible endpoint copied earlier

Info

You can create your own shared access policy. But the portal needs at least the Service Connect permission

"},{"location":"dev-guide/migrations/v4-to-v5/","title":"Migrate from v4 to v5","text":"

In this v5, the major change is the integration of AWS in the portal. Some changes have also been made at the portal web app settings.

"},{"location":"dev-guide/migrations/v4-to-v5/#aws","title":"AWS","text":"

Starting from version 5, the portal now supports AWS integration. To learn how to deploy AWS services using the portal, please refer to the Quick Start for AWS documentation. It provides step-by-step instructions on setting up and deploying AWS resources using the portal's interface.

"},{"location":"dev-guide/migrations/v4-to-v5/#azure","title":"Azure","text":"

To migrate from v4 to v5 manually, you have to add CloudProvider with Azure as default value. You have to add also Azure__ prefix in all setting to the portal web app.

Name Setting Type Detail CloudProvider Application setting (Possible value Azure) The name of the CLoud Provider to run in the portal Azure__LoRaRegionRouterConfig__Url Application setting The Url for LoRa Region Router Configuration Azure__LoRaKeyManagement__Url Application setting The Url for LoRa Key Management Azure__LoRaKeyManagement__Code Application setting The Code for LoRa Key Management Azure__LoRaFeature__Enabled Application setting To enable or disable LoRa Feature Azure__IoTHub__ConnectionString Connection string The IotHub Connection String Azure__IoTHub__EventHub__Endpoint Connection string The IotHub Event Hub compatible endpoint Azure__IoTHub__EventHub__ConsumerGroup Application setting (Default value iothub-portal) The name of the consumer group used to to pull data from the IoT Hub Azure__IoTDPS__ServiceEndpoint Application setting The IotDPS Service Endpoint Azure__IoTDP__LoRaEnrollmentGroup Application setting The name of the IotDPS LoRa Enrollment group Azure__IoTDPS__DefaultEnrollmentGroup Application setting The name of the default IotDPS Enrollment group Azure__IoTDPS__ConnectionString Connection string The IotDPS Connection String"},{"location":"dev-guide/testing/unit-tests-common-practices/","title":"Unit Tests Common Practices","text":""},{"location":"dev-guide/testing/unit-tests-common-practices/#naming-conventions","title":"Naming Conventions","text":""},{"location":"dev-guide/testing/unit-tests-common-practices/#test-class","title":"Test class","text":"

The test class should follow the naming convention [ClassUnderTest]Tests.

Example: The test class for a class named ProductController should be named ProductControllerTests:

C#
[TestFixture]\npublic class ProductControllerTests\n{\n    ...\n}\n
"},{"location":"dev-guide/testing/unit-tests-common-practices/#test-method","title":"Test method","text":"

The test method should follow the naming convention [MethodUnderTest]_[BehaviourToTest]_[ExpectedResult].

Example: A method named GetProduct should be tested to see if it returns an existing product. The name of the test should be GetProduct_ProductExist_ProductReturned:

C#
[Test]\npublic async Task GetProduct_ProductExist_ProductReturned()\n{\n    ...\n}\n
"},{"location":"dev-guide/testing/unit-tests-common-practices/#unit-test-skeleton-three-stepsparts","title":"Unit Test Skeleton: Three Steps/Parts","text":"

A unit test should be devided into three steps:

  1. Arrange: The first part where the input/expected data are defined
  2. Act: The second part where the behavior under test is executed
  3. Assert: The third and final part where assertions are made

These three parts are visually defined with comments so that unit tests are humanly comprehensible:

C#
[Test]\npublic async Task GetProduct_ProductExist_ProductReturned()\n{\n    // Arrange\n    var productId = Guid.NewGuid().ToString();\n    var expectedProduct = new Product\n    {\n        Id = productId\n    };\n\n    // Act\n    var product = this.productService.GetProduct(productId);\n\n    // Asset\n    _ = product.Should().BeEquivalentTo(expectedProduct);\n}\n

Tip

On the IoT Hub portal, we use the fluentassertions library for unit tests for natural/human reusable assertions.

"},{"location":"dev-guide/testing/unit-tests-common-practices/#mock","title":"Mock","text":"

A unit test should only test its assigned layer. Any lower layer that requires/interacts with external resources should be mocked to ensure sure that the unit tests are idempotent.

Note

Example: We want to implement unit tests for a controller that requires three services. Each service depends on other services/repositories/http clients that need external resources like databases, APIs... Any execution of unit tests that depend on these external resources can be altered (not idempotent) because they depend on the uptime and data of these resources.

On the IoT Hub portal, we use the library Moq for mocking within unit tests:

C#
[TestFixture]\npublic class ProductControllerTests\n{\n    private MockRepository mockRepository;\n    private Mock<IProductRepository> mockProductRepository;\n\n    private IProductService productService;\n\n    [SetUp]\n    public void SetUp()\n    {\n        // Init MockRepository with strict behaviour\n        this.mockRepository = new MockRepository(MockBehavior.Strict);\n        // Init the mock of IProductRepository\n        this.mockProductRepository = this.mockRepository.Create<IProductRepository>();\n        // Init the service ProductService. The object mock ProductRepository is passed the contructor of ProductService\n        this.productService = new ProductService(this.mockProductRepository.Object);\n    }\n\n    [Test]\n    public async Task GetProduct_ProductExist_ProductReturned()\n    {\n        // Arrange\n        var productId = Guid.NewGuid().ToString();\n        var expectedProduct = new Product\n        {\n            Id = productId\n        };\n\n        // Setup mock of GetByIdAsync of the repository ProductRepository to return the expected product when given the correct product id\n        _ = this.mockProductRepository.Setup(repository => repository.GetByIdAsync(productId))\n                .ReturnsAsync(expectedProduct);\n\n        // Act\n        var product = this.productService.GetProduct(productId);\n\n        // Asset\n        _ = product.Should().BeEquivalentTo(expectedProduct);\n\n        // Assert that all mocks setups have been called\n        _ = MockRepository.VerifyAll();\n    }\n}\n
"},{"location":"dev-guide/testing/unit-tests-on-blazor-components/","title":"Unit Tests on Blazor components","text":"

Info

To test Blazor components on the Iot Hob Portal, we use the library bUnit

"},{"location":"dev-guide/testing/unit-tests-on-blazor-components/#how-to-unit-test-component","title":"How to unit test component","text":"

Let us assume we have a compoment ProductDetail to test.

Example of the content of the component ProductDetail
@inject IProductService ProductService\n\n@if(product != null)\n{\n    <p id=\"product-id\">@product.Id</p>\n}\n\n@code {\n    [Parameter]\n    public string ProductId { get; set; }\n\n    private Product product;\n\n    protected override async Task OnInitializedAsync()\n    {\n        await GetProduct();\n    }\n\n    private async Task GetProduct()\n    {\n        try\n        {\n            product = await ProductService.GetProduct(ProductId);\n        }\n        catch (ProblemDetailsException exception)\n        {\n            Error?.ProcessProblemDetails(exception);\n        }\n    }\n}\n
First you have to a unit test class that extend
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n}\n

Info

The class BlazorUnitTest provides helpers/test context dedicated for unit tests for the blazor component. It also avoids code duplication of unit test classes.

Override the method Setup
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n    public override void Setup()\n    {\n        // Don't forget the method base.Setup() to initialize existing helpers\n        base.Setup();\n    }\n}\n
Setup the mockup of the service IProductService
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n    // Declare the mock of IProductService\n    private Mock<IProductService> productServiceMock;\n\n    public override void Setup()\n    {\n        base.Setup();\n\n        // Intialize the mock of IProductService\n        this.productServiceMock = MockRepository.Create<IProductService>();\n\n        // Add the mock of IProductService as a singleton for resolution \n        _ = Services.AddSingleton(this.productServiceMock.Object);\n    }\n}\n

Info

After configuring the test class setup, you can start implementing unit tests.

Below is an example of a a unit test that checks whether the GetProduct method of the serivce ProductService service was called after the component was initialized:

C#
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n    ...\n\n    [Test]\n    public void OnInitializedAsync_GetProduct_ProductIsRetrieved()\n    {\n        // Arrange\n        var expectedProduct = Fixture.Create<Product>();\n\n        // Setup mock of GetProduct of the service ProductService\n        _ = this.productServiceMock.Setup(service => service.GetProduct(expectedProduct.Id))\n            .ReturnsAsync(expectedProduct);\n\n        // Act\n        // Render the component ProductDetail with the required ProductId parameter\n        var cut = RenderComponent<ProductDetail>(ComponentParameter.CreateParameter(\"ProductId\", expectedProduct.Id));\n        // You can wait for a specific element to be rendered before assertions using a css selector, for example the DOM element with id product-id\n        _ = cut.WaitForElement(\"#product-id\");\n\n        // Assert\n        // Assert that all mocks setups have been called\n        cut.WaitForAssertion(() => MockRepository.VerifyAll());\n    }\n}\n

Tip

WaitForAssertion is useful in asserting asynchronous changes: It will blocks and waits in a test method until the specified assertion action does not throw an exception, or until the timeout is reached (the default timeout is one second). Assertion of asynchronous changes

Tip

Within unit tests on Blazor components, you can interact with HTML DOM and query rendered HTMLelements (buttons, div...) by using CSS selectors (id, class...) Lean more about CSS selectors

"},{"location":"dev-guide/testing/unit-tests-on-blazor-components/#how-to-unit-test-a-component-requiring-an-external-component","title":"How to unit test a component requiring an external component","text":"

Some components proposed by MudBlazor (MudAutocomplete, MudSelect...) use another component MudPopoverProvider to display elements. If in a unit test that uses these MudBlazor components, the MudPopoverProvider component is not rendered, the interactions with these components are restricted.

Let us start with the following example:

Example of the content of the component SearchState
<MudAutocomplete T=\"string\" Label=\"US States\" @bind-Value=\"selectedState\" SearchFunc=\"@Search\" />\n\n@code {\n    private string selectedState;\n    private string[] states =\n    {\n        \"Alabama\", \"Colorado\", \"Missouri\", \"Wisconsin\"\n    }\n\n    private async Task<IEnumerable<string>> Search(string value)\n    {\n        // In real life use an asynchronous function for fetching data from an api.\n        await Task.Delay(5);\n\n        // if text is null or empty, show complete list\n        if (string.IsNullOrEmpty(value)) \n            return states;\n        return states.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase));\n    }\n}\n

We want to test the search when a user interacts with the MudAutocomplete component to search for the state Wisconsin:

C#
[TestFixture]\npublic class SearchStateTests : BlazorUnitTest\n{\n    ...\n\n    [Test]\n    public void Search_UserSearchAndSelectState_StateIsSelected()\n    {\n        // Arrange\n        var userQuery = \"Wis\";\n\n        // First render MudPopoverProvider component\n        var popoverProvider = RenderComponent<MudPopoverProvider>();\n        // Second, rendrer the component SearchState (under unit test)\n        var cut = RenderComponent<SearchState>();\n\n        // Find the MudAutocomplete component within SearchState component\n        var autocompleteComponent = cut.FindComponent<MudAutocomplete<string>>();\n\n        // Fire click event on, \n        autocompleteComponent.Find(\"input\").Click();\n        autocompleteComponent.Find(\"input\").Input(userQuery);\n\n        // Wait until the count of element in the list rendred on the component MudPopoverProvider is equals to one\n        popoverProvider.WaitForAssertion(() => popoverProvider.FindAll(\"div.mud-list-item\").Count.Should().Be(1));\n\n        // Act\n        // Get the only element present on the list\n        var stateElement = popoverProvider.Find(\"div.mud-list-item\");\n        // Fire click event on the element\n        stateElement.Click();\n\n        // Assert\n        // Check if the MudAutocomplete compoment has been closed after the click event\n        cut.WaitForAssertion(() => autocompleteComponent.Instance.IsOpen.Should().BeFalse());\n        ...\n    }\n}\n
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"IoT Hub Portal","text":"

This project aims to provide a solution for handling IoT Devices easyly. It leverages on Azure IoT Hub or AWS IoT Core for connectivity and device management.

"},{"location":"#features","title":"Features","text":""},{"location":"#quick-start","title":"Quick Start","text":""},{"location":"#azure","title":"Azure","text":"

Quick Start for Azure environment.

"},{"location":"#amazon-web-services","title":"Amazon Web Services","text":"

Quick Start for AWS environment.

"},{"location":"#known-issues-and-limitations","title":"Known Issues and Limitations","text":"

Refer to Known Issues for known issues, gotchas and limitations.

"},{"location":"#support","title":"Support","text":"

This is an open source solution. For bugs and issues with the codebase please log an issue in this repo.

"},{"location":"#credits","title":"Credits","text":""},{"location":"aws/","title":"AWS configurations","text":""},{"location":"aws/#overall-architecture","title":"Overall Architecture","text":"

This schema represent the various components and how they interact to have a better understanding of the various solution elements.

  1. The user is authenticated by the OpenID Connect server.
  2. The user access to the IoT Hub Portal with the OAuth2.0 token.
  3. The IoT Hub portal uses the AWS IoT REST API to retrieve the data.
  4. The IoT Hub portal uses the AWS S3 storage to store the device models configuration (Images, Commands, etc.).
  5. The IoT Hub portal synchronizes its data with the IoT Hub to provide a consistent view of the data.
"},{"location":"aws/#quick-start","title":"Quick Start","text":""},{"location":"aws/#prerequisites","title":"Prerequisites","text":""},{"location":"aws/#deployed-resources","title":"Deployed Resources","text":"

The template will deploy in your AWS Account the Following resources:

"},{"location":"aws/#instructions","title":"Instructions","text":"
  1. Choose a stack name for your AWS Deployment.

  2. Follow next step below to start your deployment:

    1. Press on the button here below to download the template AWS: Download the template

    2. Import your template : From the AWS console: CloudFormation new stack - You can change the deployment region by changing the region directly in the URL - In \"Upload a template file\" import the previously downloaded file With AWS CLI:

      Bash Session
      aws cloudformation deploy --template /path_to_template/awsdeploy.yml --stack-name your-stack-name --region your-region\n

      see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-cli-deploy.html for more option about aws cloudformation deploy

  3. You will get to a page asking you to fill the following fields:

    • Stack Name: A name for the stack where all the template resource would be put into, just choose a meaningful name.
    • PostgreSQL user: The PostgreSQL user name to be used for the IoT Hub Portal database.
    • PostgreSQL password: The PostgreSQL password to be used for the IoT Hub Portal database.
    • AWS Access Key: The AWS Access Key of your AWS environment.
    • AWS Access Secret Key: The AWS Access Secret Key of your AWS environment.
    • Api Client Id: the ID of the API client that will be used to authenticate the portal.
    • Client Id: the ID of the web client that will be used to authenticate the portal.
    • Open Id Authority: The OpenID authority used by the portal.
    • OpenId Metadata URL: The OpenID metadata URL used by the portal.
    • OpenId Scope Name: The Open ID Scope name
"},{"location":"aws/#configurations","title":"Configurations","text":"

This configurations are used to get access to AWS cloud Platform. You have to enter them in a json file to be able to connect to the Iot Hub Portal. Here is a template of a such json file.

JSON
{\n  \"CloudProvider\": \"AWS\",\n  \"AWS:Access\": \"<ACCESS_KEY>\",\n  \"AWS:AccessSecret\": \"<ACCESS_SECRET_KEY>\",\n  \"AWS:Region\": \"<REGION_KEY>\",\n  \"AWS:BucketName\": \"<BUCKET_NAME>\",  \n  \"AWS:AccountId\": \"<ACCOUNT_IDENTIFIER>\",\n  \"AWS:GreengrassRequiredRoles:<ID>\": \"<GREENGRASS_ROLE_NAME>\",\n  \"OIDC:Scope\": \"<SCOPE>\",\n  \"OIDC:MetadataUrl\": \"<METADATA_URL>\",\n  \"OIDC:ClientId\": \"<CLIENT_ID>\",\n  \"OIDC:Authority\": \"<AUTHORITY>\",\n  \"OIDC:ApiClientId\": \"<API_CLIENT_ID>\",\n  \"PostgreSQL:ConnectionString\": \"<POSTGRE_SQL_CONNECTION_STRING>\"\n}\n

Note: You must replace all values in the brackets by your own AWS settings. If you can't find them in the AWS Portal, please contact an administrator of this project to have more information.

You are now ready to start your IoT Hub Portal development !

"},{"location":"azure/","title":"Azure Configurations","text":""},{"location":"azure/#overall-architecture","title":"Overall Architecture","text":"

This schema represent the various components and how they interact to have a better understanding of the various solution elements.

  1. The user is authenticated by the OpenID Connect server.
  2. The user access to the IoT Hub Portal with the OAuth2.0 token.
  3. The IoT Hub portal uses the Azure IoT Hub REST API to retrieve the data.
  4. The IoT Hub portal uses the Azure Device Provisioning Service to manage IoT Edge devices.
  5. The IoT Hub portal uses the Azure Storage account to store the device models configuration (Images, Commands, etc.).
  6. The IoT Hub portal uses the LoRa Key Management Facade to send Cloud to Device (C2D) messages to LoRa devices.
  7. The LoRa Key Management Facade uses Redis to store its cached data.
  8. The LoRa Key Management Facade uses the Azure IoT Hub REST API to retrieve the LoRa device keys and send C2D messages.
  9. The IoT Hub portal synchronizes its data with the IoT Hub to provide a consistent view of the data.

Note: For more information about the LoRa Key Management Facade, see the Azure IoT Edge LoRaWAN Starter Kit page.

"},{"location":"azure/#quick-start","title":"Quick Start","text":""},{"location":"azure/#prerequisites","title":"Prerequisites","text":""},{"location":"azure/#deployed-resources","title":"Deployed Resources","text":"

The template will deploy in your Azure subscription the Following resources:

"},{"location":"azure/#instructions","title":"Instructions","text":"
  1. Choose a solution prefix for your Azure Deployment.

  2. Configure your AD to connect to the portal. Use Portal AD applications configuration page to configure your AD B2C Tenant :

    You should have recorded the following information:

    • OpenID authority: <your-openid-authority>
    • OpenID metadata URL: <your-openid-provider-metadata-url>
    • Client ID: <your-client-id>
    • API Client ID: <your-client-id>
  3. Press on the button here below to start your deployment on Azure:

  4. You will get to a page asking you to fill the following fields:

    • Resource Group: A logical \"folder\" where all the template resource would be put into, just choose a meaningful name.
    • Location: In which DataCenter the resources should be deployed. Make sure to choose a location where IoT Hub is available
    • Unique Solution Prefix: A string that would be used as prefix for all the resources name to ensure their uniqueness.
    • PostgreSQL user: The PostgreSQL user name to be used for the IoT Hub Portal database.
    • PostgreSQL password: The PostgreSQL password to be used for the IoT Hub Portal database.
    • Confirm PostgreSQL password: The PostgreSQL password to be used for the IoT Hub Portal database.
    • Open Id Authority: The OpenID authority used by the portal.
    • OpenId Metadata URL: The OpenID metadata URL used by the portal.
    • Client Id: the ID of the web client that will be used to authenticate the portal.
    • Api Client Id: the ID of the API client that will be used to authenticate the portal.
    • Edge gateway name: the name of your LoRa Gateway node in the IoT Hub.
    • Deploy Device: Do you want demo end devices to be already provisioned (one using OTAA and one using ABP)? If yes set this to true, the code located in the Arduino folder would be ready to use immediately.
    • Reset pin: The reset pin of your gateway (the value should be 7 for the Seed Studio LoRaWan, 25 for the IC880A)
    • Region: In what region are you operating your device (currently only EU868 and US915 is supported)

    see: https://azure.github.io/iotedge-lorawan-starterkit/dev/quickstart/#deployed-azure-infrastructure for more information about the LoRaWan IoT Hub and Azure deployment.

"},{"location":"azure/#configurationssecrets","title":"Configurations/Secrets","text":"

Secrets are used to fill in the login credentials to the cloud platform. You have to enter them in a json file to be able to connect to the IoT Hub Portal. Here is a template of a such json file :

JSON
{\n  \"CloudProvider\": \"Azure\",\n  \"OIDC:Scope\": \"<SCOPE>\",\n  \"OIDC:MetadataUrl\": \"<METADATA_URL>\",\n  \"OIDC:ClientId\": \"<CLIENT_ID>\",\n  \"OIDC:Authority\": \"<AUTHORITY>\",\n  \"OIDC:ApiClientId\": \"<API_CLIENT_ID>\",\n  \"LoRaFeature:Enabled\": \"<TRUE_OR_FALSE>\",\n  \"Azure:LoRaRegionRouterConfig:Url\": \"<LORA_WAN_ROUTER_CONFIGURATION_URL>\",\n  \"Azure:LoRaKeyManagement:Url\": \"<LORA_WAN_KEY_MANAGEMENT_URL>\",\n  \"Azure:LoRaKeyManagement:Code\": \"<LORA_WAN_KEY_MANAGEMENT_CODE>\",\n  \"Kestrel:Certificates:Development:Password\": \"<DEV_PASSWORD>\",\n  \"Azure:IoTHub:ConnectionString\": \"<IOT_HUB_CONNECTION_STRING>\",\n  \"Azure:IoTHub:EventHub:Endpoint\": \"<IOT_HUB_EVENT_HUB_ENDPOINT>\",\n  \"Azure:IoTHub:EventHub:ConsumerGroup\": \"<IOT_HUB_EVENT_HUB_CONSUMER_GROUP>\",\n  \"Azure:IoTDPS:ServiceEndpoint\": \"<SERVICE_END_POINT>\",\n  \"Azure:IoTDPS:LoRaEnrollmentGroup\": \"<LORA_WAN_ENROLLMENT_GROUP>\",\n  \"Azure:IoTDPS:DefaultEnrollmentGroup\": \"<LORA_WAN_DEFAULT_ENROLLMENT_GROUP>\",\n  \"Azure:IoTDPS:ConnectionString\": \"<IOT_DPS_CONNECTION_STRING>\",\n  \"PostgreSQL:ConnectionString\": \"<POSTGRE_SQL_CONNECTION_STRING>\",\n  \"Azure:StorageAccount:ConnectionString\": \"<CONNECTION_STRING_STORAGE_ACCOUNT>\"\n}\n

Note: You must replace all values in the brackets by your own Azure settings. If you can't find them in the Azure Portal, please contact an administrator of this project to have more information.

This json file must be added into your project solution. To do that, click on the AzureIoTHub.Server project in Visual Studio and select Manage User Secrets from the context menu. You can now add your secrets inside this file.

You are now ready to start your IoT Hub Portal development !

"},{"location":"b2c-applications/","title":"Azure AD B2C Tenant with applications","text":"

This solution uses Azure AD B2C to authenticate the portal. In this page you will configure the B2C tenant and two applications (API and Web UI).

By the end, you should have recorded the following information:

Text Only
* OpenID authority: `<your-openid-authority>`\n* OpenID metadata URL: `<your-openid-provider-metadata-url>`\n* Client ID: `<your-client-id>`\n* API Client ID: `<your-client-id>`\n
"},{"location":"b2c-applications/#step-by-step-instructions","title":"Step by Step instructions","text":"
  1. Create an Azure AD B2C Tenant (see: https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant#create-an-azure-ad-b2c-tenant)

    • Record the tenant ID and the tenant name.
  2. After creating your Azure AD B2C Tenant and registering your applications, you need to set up OpenID Connect to secure your applications. Here\u2019s how to find your OpenID authority and OpenID metadata URL:

    1. Determine your OpenID Authority:
      • Your OpenID Authority is the issuer URL of your Azure AD B2C Tenant. It typically follows the format: https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/.
      • Replace <tenant-name> with your actual tenant name.
    2. Find your OpenID Metadata URL:
      • The OpenID Metadata URL for Azure AD B2C tenants is usually in the format: https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=<policy-name>.
      • Replace <tenant-name> and <policy-name> with your actual tenant name and the policy name you are using (like B2C_1_SignUpSignIn).
    3. Make sure to record the OpenID authority and OpenID metadata URL for future configuration steps.
  3. Configure the requiered AD Applications.

    1. Create the IoT Hub Portal API Application:

      • Select App registrations, and then select New registration.
      • Enter a Name for the application. For example, IoT Hub Portal.
      • Under Redirect URI, select Web, and then enter an expected endpoint for your portal (ex: https://tenantName.b2clogin.com/tenantName.onmicrosoft.com/oauth2/authresp)
      • Select Register.
      • Record the Application (client) ID for use in your web API's code.
      • Under Manage, select Certificates & Secrets.
      • Under Client secret, select New client secret.
      • Enter a name for the secret.
      • Record the Client secret for use in your web API's code.
      • Under Manage, select API permissions.
      • Under Configured permissions, select Add a permission.
      • Select the Microsoft APIs tab.
      • Under Commonly used Microsoft APIs, select Microsoft Graph.
      • Select Application permissions.
      • Under Application permissions, expand User, then select:
        • User.Invite.All
        • User.ManageIdentities.All
        • User.Read.All
        • User.ReadWrite.All
      • Select Add permission.
      • If you're prompted to select an account, select your currently signed-in administrator account, or sign in with an account in your Azure AD B2C tenant that's been assigned at least the Cloud application administrator role.
      • Under Manage, select Expose an API.
      • Next to Application ID URI, select the Set link.
      • Under Scopes defined by this API, select Add a scope.
      • Enter the following values to create a scope that defines read access to the API, then select Add scope:
        • Scope name: API.Access (this is the name of the scope that will be used in the template)
        • Admin consent display name: Access to the Portal API
        • Admin consent description: Allows the application to get access to the Portal API
    2. Create the IoT Hub Portal Client Application:

      • Select App registrations, and then select New registration.
      • Enter a Name for the application. For example, IoT Hub Portal Client.
      • Under Redirect URI, select Web, and then enter an expected endpoint for your portal (ex: https://**solutionPrefix**portal.azurewebsites.net/authentication/login-callback)
      • Select Register.
      • Record the Application (client) ID for use in your web client.
      • Select App registrations, and then select the web application that should have access to the API.
      • Under Manage, select API permissions.
      • Under Configured permissions, select Add a permission.
      • Select the My APIs tab.
      • Select the API to which the web application should be granted access.
      • Under Permission, expand API, and then select the scope that you defined earlier.
      • Select Add permissions.
      • Select Grant admin consent for (your tenant name).
      • If you're prompted to select an account, select your currently signed-in administrator account, or sign in with an account in your Azure AD B2C tenant that's been assigned at least the Cloud application administrator role.
      • Select Yes.
      • Select Refresh, and then verify that \"Granted for ...\" appears under Status for both scopes.
  4. Configure the required User flow:

    1. Select User flows, and then select New user flow.
    2. Under Select a user flow type, select Sign in, then select Create.
    3. Enter a name for the flow SignIn, then select Create.
"},{"location":"concepts/","title":"Core Concepts","text":"

The Azure IoT Hub portal inherits from Azure IoT Hub concepts to manage IoT devices.

It relies on the following concepts:

"},{"location":"concepts/#device-models","title":"Device models","text":"

By using this capability, the application can create logical representations of IoT devices. This feature is designed to configure a set of sharable properties between devices. When creating a device, the user is asked to specify the device model. The application will then apply the properties of the device model to the device.

"},{"location":"concepts/#parameters","title":"Parameters","text":""},{"location":"concepts/#built-in-models","title":"Built-in models","text":"

Built-in models are predefined device models that can be used by the application. This functionality is exactly the same as standard device models except that the properties are not editable and the device model is not removable via the Portal.

Note: Creating and updating built-in models is not available in the portal. They can be managed using the Azure IoT Hub portal APIs.

See Device Model API reference for more information.

"},{"location":"concepts/#devices","title":"Devices","text":"

Devices are the physical IoT devices that are provisioned by the application. They are represented by an object that is stored in the Azure IoT Hub as the device twin.

"},{"location":"concepts/#device-parameters","title":"Device Parameters","text":""},{"location":"concepts/#device-twin-tags","title":"Device Twin tags","text":"

To store additional information about the device, the application uses device twin tags.

Name Position Description deviceName tags.deviceName Field that contains the device friendly name.note: if not set, the portal will show the device id instead of the device name until it's configured. modelId tags.modelId Field that contains the device model identifier that the device is related.note: if not set, the device is not usable on the IoT hub portal. supportLoRaFeatures tags.supportLoRaFeatures Field that specifies if the device must support LoRa features.note: if not set, LoRa features will be available on the device."},{"location":"concepts/#iot-edge","title":"IoT Edge","text":"

IoT Edge is fully herited from Azure IoT Hub concepts. In the portal, the user can mangage the IoT Edge devices stored in Azure IoT Hub. For more information about Azure IoT Edge, see Azure IoT Edge documentation.

"},{"location":"concepts/#iot-edge-parameters","title":"IoT Edge Parameters","text":""},{"location":"concepts/#last-deployment","title":"Last deployment","text":"

The last deployment section shows information about the deployment manifest that is currently applied to the IoT Edge device.

"},{"location":"concepts/#iot-edge-module","title":"IoT Edge Module","text":"

The IoT Edge module section represents the modules that are currently deployed on the IoT Edge device. It doesn't include the system modules of IoT Edge (edgeAgent and edgeHub). With the portal, the user can interact with these modules and manage them (Get last module logs, restart module, etc.).

"},{"location":"concepts/#iot-edge-device-twin-tags","title":"IoT Edge Device Twin tags","text":"

To store additional information about the device, the application uses device twin tags.

Name Position Description Environment tags.env Field that contains the Device Environment value.note: this tag may be used to target deployment manifests for the IoT Edge Type tags.type Field that contains The type of the IoT Edge device.note: this tag may be used to target deployment manifests for the IoT Edge"},{"location":"concepts/#dps-enrollment-groups","title":"DPS Enrollment groups","text":"

The IoT Hub portal relies on Azure Device Provisioning Enrollement groups to manage IoT Edge device connection strings. When clicking on \"Connect\" in the IoT Edge details page, the user can access the device unique credentials in the enrollment group.

Note: see Provision the device with its cloud identity to know how to configure the IoT Edge to use these credentials to connect to the platform.

"},{"location":"concepts/#device-configuration","title":"Device Configuration","text":"

By using the portal, users can manage the device configuration and deploy to devices that are targeted by the configuration.

It relies on the Device Model to define the configuration parameters that can be deployed to the devices.

"},{"location":"concepts/#device-configuration-template","title":"Device Configuration template","text":"

Under the cover, the configuration is stored in the Azure IoT Hub as the twin configuration.

JSON
{\n    \"id\": \"<configuration-name>-<timestamp>\",\n    \"schemaVersion\": \"1.0\",\n    \"labels\": {\n        \"created-by\": \"Azure IoT hub Portal\",\n        \"configuration-id\": \"<configuration-name>\"\n    },\n    \"content\": {\n        \"deviceContent\": {\n            \"properties.desired.**\": **\n        }\n    },\n    \"targetCondition\": \"tags.modelId = '<The model identifier>' and tags.** = '**' AND ...\",\n    \"createdTimeUtc\": \"2022-06-13T07:32:19.7376998Z\",\n    \"lastUpdatedTimeUtc\": \"2022-06-13T07:32:19.7376998Z\",\n    \"priority\": 100\n}\n

Please note that the created-by label is used to identify the configuration created by the IoT Hub portal.

"},{"location":"concepts/#iot-edge-configuration","title":"IoT Edge Configuration","text":"

The IoT Edge configuration concerns the IoT Edge deployment manifests that are currently present in the IoT Hub. The portal can be used to see the details of the configurations.

Note: At this time the portal cannot be used to update the configurations.

"},{"location":"concepts/#target","title":"Target","text":"

The parameters are related to the IoT Edge deployment manifest target condition field. The IoT Hub portal will use the Target condition to extract this values from the deployment manifest.

Name Position Description Owner tags.owner Owner tag filter condition from the Deployment Manifest. Environment tags.env Environment tag filter from the Deployment Manifest. Type tags.type Device type tag filter from the Deployment Manifest. Expected value for IoT Edge LoRaWAN LNS is LoRa Network Server, otherwise the value must be Other"},{"location":"concepts/#enrollment-groups","title":"Enrollment groups","text":"

The IoT Hub portal relies on Azure Device Provisioning Enrollment groups to manage IoT device connection credentials.

For each device model, the portal will create a new enrollment group with symmetric key attestation. By clicking on \"Connect\" in the device details page, the portal will show unique credentials to the device for the corresponding enrollment group.

Furthermore, the enrollment group is configured to provide initial device twin state:

JSON
{\n  \"tags\": {\n    \"modelId\": \"......\"\n  },\n  \"properties\": {\n    \"desired\": {}\n  }\n}\n

For more information, see Azure Device Provisioning Enrollement groups.

"},{"location":"concepts/#lorawan","title":"LoRaWAN","text":"

LoRaWAN features are activated by default, providing a way to configure IoT Devices that supports LoRaWAN connectivity in the Portal. Internally, the LoRaWAN connectivity is expected to be provided by IoTEdge LoRaWAN StarterKit. The IoT Hub portal will manage devices by modifying their twin properties to make them working with this solution.

Note: to disable LoRa Features, change the value of LoRaFeature__Enabled to false in the Portal App Settings.

"},{"location":"concepts/#lorawan-device-models","title":"LoRaWAN Device Models","text":"

For regular Device Models the IoT hub portal provides the possibility to manage LoRaWAN device models. To activate the LoRaWAN features on the device model, the user have to enable the option in the LoRa Device section

Note: once activated, the device model detail adds a new tab called \"LORAWAN\" that adds new settings to the device model.

"},{"location":"concepts/#lorawan-device-model-parameters","title":"LoRaWAN Device Model Parameters","text":"

The parameters for the device models are parameters that are stored in the IoT Hub portal and retrieved for devices that inherits from this device model.

Note: When changing the value of a parameter, the device will be updated with the new value. In that case, user should then modify each device and re-save it to get the correct properties.

Note: for more information about LoRaWAN properties, please refer to the LoRaWAN StarterKit Documentation

"},{"location":"concepts/#commands","title":"Commands","text":"

The devices commands are pre-stored frames that the user can add to the device model and then will be able to use on the device detail page to launch to the device.

"},{"location":"concepts/#lorawan-devices","title":"LoRaWAN Devices","text":"

LoRaWAN devices are accessible from the IoT Hub portal for devices that inherits from the LoRaWAN device model. The LoRaWAN tab shows the device details.

Note: By selecting the correct device model on the first tab, the portal will automatically take LoRaWAN settings from the device model to apply on the device.

"},{"location":"concepts/#concentrators","title":"Concentrators","text":""},{"location":"concepts/#concentrator-parameters","title":"Concentrator Parameters","text":""},{"location":"concepts/#concentrator-tags","title":"Concentrator Tags","text":"

To store additional information about the concentrator, the application will use the target device to extract values:

Name Position Description Device Name tags.deviceName Field that contains the Device name. Region tags.loraRegion Field that contains the Device region. deviceType tags.deviceType Field that contains The type of device. Expected value is LoRa Concentrator"},{"location":"concepts/#command-execution","title":"Command Execution","text":"

To execute the command, the device should have joined the network. The message below explains that the device have to be connected to the network ant commands are disabled until the device is connected to the network.

"},{"location":"concepts/#command-execution-flow","title":"Command execution flow","text":"

The schema below explain how the command execution flow works.

sequenceDiagram User->>+IoT Hub Portal: Send Command (DeviceId, FrameId) IoT Hub Portal->>+LoRa Key Management Facade: POST /api/cloudtodevicemessage LoRa Key Management Facade->>+Azure IoT Hub: Invoke Device Method to Network Server Azure IoT Hub-->>-LoRa Key Management Facade: Cloud To Device Method Result LoRa Key Management Facade-->>-IoT Hub Portal: Send Cloud To Device Message Result IoT Hub Portal-->>-User: Command send result

See https://azure.github.io/iotedge-lorawan-starterkit/2.0.0/quickstart/#cloud-to-device-message for more information about the Cloud To Device Message involed in the LoRa WAN device commands execution flow.

"},{"location":"concepts/#automatic-device-configuration-for-lora-wan","title":"Automatic device configuration for LoRa WAN","text":"

When modifying the device model, the IoT Hub portal will automatically create a new Device Configuration that will target the IoT devices that have the corresponding modelId tag.

The IoT Hub portal will create a new Rollout deployment that will remove older configuration and add the new configuration.

This process ensure that the devices twin will be updated at scale by the IoT hub and each devices that inherit for the model will be updated according the model configuration.

The configuration will be created with the following schema:

JSON
{\n    \"id\": \"<model-name>-<timestamp>\",\n    \"schemaVersion\": \"1.0\",\n    \"labels\": {\n        \"created-by\": \"Azure IoT hub Portal\"\n    },\n    \"content\": {\n        \"deviceContent\": {\n            \"properties.desired.AppEUI\": \"<The Device Model OTAA AppEUI>\",\n            \"properties.desired.SensorDecoder\": \"<The Device Model Sensor Decoder>\"\n        }\n    },\n    \"targetCondition\": \"tags.modelId = '<The model identifier>'\",\n    \"createdTimeUtc\": \"2022-02-28T20:29:39.128Z\",\n    \"lastUpdatedTimeUtc\": \"2022-02-28T20:29:39.128Z\",\n    \"priority\": 0\n}\n

Please note that the created-by label is used to identify the configuration created by the IoT Hub portal.

For more information see Automatic IoT device and module management.

"},{"location":"dev-guide/","title":"Developer Guide","text":""},{"location":"dev-guide/#directory-structure","title":"Directory Structure","text":"

The code is organized into the following directory structure:

"},{"location":"dev-guide/#overall-architecture","title":"Overall Architecture","text":"

This schema represent the various components and how they interact to have a better understanding of the various solution elements.

  1. The user is authenticated by the OpenID Connect server.
  2. The user access to the IoT Hub Portal with the OAuth2.0 token.
  3. The IoT Hub portal uses the Azure IoT Hub REST API to retrieve the data.
  4. The IoT Hub portal uses the Azure Device Provisioning Service to manage IoT Edge devices.
  5. The IoT Hub portal uses the Azure Storage account to store the device models configuration (Images, Commands, etc.).
  6. The IoT Hub portal uses the LoRa Key Management Facade to send Cloud to Device (C2D) messages to LoRa devices.
  7. The LoRa Key Management Facade uses Redis to store its cached data.
  8. The LoRa Key Management Facade uses the Azure IoT Hub REST API to retrieve the LoRa device keys and send C2D messages.
  9. The IoT Hub portal synchronizes its data with the IoT Hub to provide a consistent view of the data.

Note: For more information about the LoRa Key Management Facade, see the Azure IoT Edge LoRaWAN Starter Kit page.

"},{"location":"dev-guide/#prerequisites","title":"Prerequisites","text":"

The following should be completed before proceeding with the IoT Hub Portal development or deployment in your environment.

Before getting started, it is better to master the tools below:

Once you know the basics of these technologies and tools, you must follow these last steps to set up your working environment.

Once you have download Docker, you must install the WSL 2 Linux kernel. To do that, please refer to the official Microsoft documentation. You can choose the linux distribution of your choice, for example Ubuntu.

"},{"location":"dev-guide/#secrets","title":"Secrets","text":"

Secrets are used to fill in the login credentials to the cloud platform. You have to enter them in a json file to be able to connect to the IoT Hub Portal. Here is a template of a such json file :

JSON
{\n  \"StorageAccount:ConnectionString\": \"<CONNECTION_STRING_STORAGE_ACCOUNT>\",\n  \"StorageAccount:BlobContainerName\": \"<BLOB_CONTAINER_NAME>\",\n  \"OIDC:Scope\": \"<SCOPE>\",\n  \"OIDC:MetadataUrl\": \"<METADATA_URL>\",\n  \"OIDC:ClientId\": \"<CLIENT_ID>\",\n  \"OIDC:Authority\": \"<AUTHORITY>\",\n  \"OIDC:ApiClientId\": \"<API_CLIENT_ID>\",\n  \"LoRaRegionRouterConfig:Url\": \"<LORA_WAN_ROUTER_CONFIGURATION_URL>\",\n  \"LoRaKeyManagement:Url\": \"<LORA_WAN_KEY_MANAGEMENT_URL>\",\n  \"LoRaKeyManagement:Code\": \"<LORA_WAN_KEY_MANAGEMENT_CODE>\",\n  \"LoRaFeature:Enabled\": \"<TRUE_OR_FALSE>\",\n  \"Kestrel:Certificates:Development:Password\": \"<DEV_PASSWORD>\",\n  \"IoTHub:ConnectionString\": \"<IOT_HUB_CONNECTION_STRING>\",\n  \"IoTHub:EventHub:Endpoint\": \"<IOT_HUB_EVENT_HUB_ENDPOINT>\",\n  \"IoTHub:EventHub:ConsumerGroup\": \"<IOT_HUB_EVENT_HUB_CONSUMER_GROUP>\",\n  \"IoTDPS:ServiceEndpoint\": \"<SERVICE_END_POINT>\",\n  \"IoTDPS:LoRaEnrollmentGroup\": \"<LORA_WAN_ENROLLMENT_GROUP>\",\n  \"IoTDPS:DefaultEnrollmentGroup\": \"<LORA_WAN_DEFAULT_ENROLLMENT_GROUP>\",\n  \"IoTDPS:ConnectionString\": \"<IOT_DPS_CONNECTION_STRING>\",\n  \"PostgreSQL:ConnectionString\": \"<POSTGRE_SQL_CONNECTION_STRING>\"\n}\n

Note: You must replace all values in the brackets by your own Azure settings. If you can't find them in the Azure Portal, please contact an administrator of this project to have more information.

This json file must be added into your project solution. To do that, click on the AzureIoTHub.Server project in Visual Studio and select Manage User Secrets from the context menu. You can now add your secrets inside this file.

You are now ready to start your IoT Hub Portal development !

"},{"location":"dev-guide/#iot-hub-portal-configuration","title":"IoT Hub Portal Configuration","text":"

By deploying the IoT Hub Portal, the user can configure the IoT Hub and the LoRaWAN network.

Since the IoT Hub Portal is deployed as a Docker container, the application settings can be configured with environment variables.

"},{"location":"dev-guide/#application-settings","title":"Application settings","text":"

Here are different settings that the user can configure:

"},{"location":"dev-guide/#connection-strings","title":"Connection strings","text":"

Here are different connection strings that the user can configure:

Note: For a production environment, an Azure Key Vault is advised to store the connection strings.

"},{"location":"dev-guide/#optional-security-settings","title":"Optional Security Settings","text":"

There are several optional security settings that the user can configure. These settings are not required for the Portal to work. By default the Portal is configured to set security levels to Microsoft.IdentityModel.Tokens defaults but the user can override these settings.

"},{"location":"dev-guide/#device-tags","title":"Device tags","text":"

The IoT Hub portal uses some tags to configure the devices. The tags are stored in the Azure IoT Hub in Device Twins.

"},{"location":"dev-guide/#storage-account","title":"Storage Account","text":"

The Storage Account is used to store the device models images. You can use the same Storage Account that is used by the LoRa Key Management Facade. This solution will use tables and blob storage to store its data. There is no need to create the containers, the application will do it for you.

"},{"location":"dev-guide/#blob-storage","title":"Blob Storage","text":"

The application uses the following blob storage:

"},{"location":"dev-guide/#working-with-the-documentation","title":"Working with the documentation","text":"

This documentation site is build using Material for MkDocs and Mike.

docs/main is a detached branch that is locked and only accepts PRs. On PR merge, Github Pages will automatically update the documentation website.

"},{"location":"dev-guide/#how-to-update-the-documentation","title":"How to update the documentation","text":"
  1. Checkout the branch that contains the documentation:

    Bash Session
    git checkout origin/docs/main\ngit checkout -b docs/<your_branch_name> \n
  2. Install dependencies

    Bash Session
    pip install -r requirements.txt\n
  3. Previewing as you write

    Bash Session
    mkdocs serve\n
  4. PRs are gated by a markdownlint check. You should use markdownlint to lint any new changes on documentation. For example you can use the vs code extension https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint

  5. Update the documentation
  6. Commit your changes
  7. Push your changes to the branch
  8. Create a PR
"},{"location":"dev-guide/#customization","title":"Customization","text":"

Refer to Material for MkDocs documentations:

"},{"location":"dev-guide/#versioning","title":"Versioning","text":"

Mike is used to generate automatically a new documentation version when a release has been published, using ci/cd pipelines.

For manual workflows (e.g. delete or retitle an existing version), please refer to Mike documentation

"},{"location":"dev-guide/#problem-details","title":"Problem Details","text":"

On IoT Hub Portal, we use the library Hellang.Middleware.ProblemDetails which implements RFC7807 to describe issues/problems that occurred on backend.

"},{"location":"dev-guide/#handle-a-new-exception-using-problem-details","title":"Handle a new exception using Problem Details","text":"

\ud83d\udca1 You can also map exceptions from dotnet framework and third parties.

"},{"location":"dev-guide/#handle-problem-details-exceptions-on-frontend","title":"Handle Problem Details exceptions on frontend","text":"

On frontend, http client uses a delegating handler ProblemDetailsHandler to:

On Blazor views, http calls must be catched to capture any exceptions of type ProblemDetailsException to be able to execute any business code to process them.

When an http call fails, the user must be notified visually by the application: A component Error has been made to respond to this use case. Below an example on how to:

C#
@code {\n    // Inject the reference to the Error component as a cascading parameter\n    [CascadingParameter]\n    public Error Error {get; set;}\n\n    private await Task GetData()\n    {\n        try\n        {\n            // Execute an http request\n        }\n        catch (ProblemDetailsException exception)\n        {\n            // Pass the ProblemDetailsException exception to Error component using its method ProcessProblemDetails()\n            // The Error component will alert the user by showing a (snackbar/dialog) using the content of the exception\n            Error?.ProcessProblemDetails(exception)\n        }\n    }\n}\n
"},{"location":"dev-guide/#how-to-install-entity-framework-core","title":"How to install Entity Framework Core","text":"

Follow the next step to install EF Core:

  1. Open the terminal and run this command:

    Bash Session
    dotnet tool install --global dotnet-ef\n
"},{"location":"dev-guide/#how-to-create-entityframework-migrations-for-postgresql-and-mysql","title":"How to create EntityFramework migrations for PostgreSQL and MySQL","text":"

For the project need, we need two database providers which are PostgreSQL and MySQL, which led us to review the architecture set up for the EntityFramework migrations. Here is a diagram showing the two architectures.

C4Deployment\n  title Architecture for multiple providers\n\n  Deployment_Node(provider1, \"Provider1\", \"Provider1\"){\n    Container(provider1dbcontextfactory, \"Provider1DbContextFactory\", \"File\", \"\")\n    Container(provider1migrations, \"Provider1Migrations\", \"Folder\", \"\")\n  }\n\n  Deployment_Node(provider2, \"Provider2\", \"Provider2\"){\n    Container(provider2dbcontextfactory, \"Provider2DbContextFactory\", \"File\", \"\")\n    Container(provider2migrations, \"Provider2Migrations\", \"Folder\", \"\")\n  }\n\n  Deployment_Node(dal, \"Infrastructure Layer\", \"Dal\"){\n    Container(dbcontext, \"DbContext\", File, \"\")\n  }\n\n  Rel(provider1dbcontextfactory, dbcontext, \"dependency\", \"\")\n  Rel(provider2dbcontextfactory, dbcontext, \"dependency\", \"\")

Follow the next steps to create EF migration:

  1. Go into the Server project folder with terminal

    Bash Session
    cd .\\IoTHub.Portal.Server\\\n
  2. Execute this command for PostgreSQL provider

    Bash Session
    dotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.Postgres\\ -v -- --DbProvider PostgreSQL\n
  3. Execute this command for MySQL provider

    Bash Session
    dotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.MySql\\ -v -- --DbProvider MySQL\n
  4. Open the created migration and follow the following steps:

    1. Move the using directive into the namespace directive

    2. Add \"_ =\" before each statement of the Up and Down methods

    3. Add the CGI copyright to the top of the file

"},{"location":"open-api/","title":"Web API Reference","text":""},{"location":"about/credits/","title":"Credits","text":""},{"location":"about/issues/","title":"Known Issues and Limitations","text":"

Refer to Known Issues for known issues, gotchas and limitations.

"},{"location":"about/license/","title":"MIT License","text":"

Copyright \u00a9 2021 CGI France

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.

"},{"location":"about/support/","title":"Support","text":"

This is an open source solution. For bugs and issues with the codebase please log an issue in this repo.

"},{"location":"dev-guide/conception/diagrams/","title":"Diagrams","text":"

In order to better understand the needs of the project, here is a use case diagram regrouping the current use cases of the project.

"},{"location":"dev-guide/conception/diagrams/#connected-objects","title":"Connected objects","text":"
graph LR\n    A[End user] --> B(Display the list of connected objects)\n    A --> C(Add a connected object)\n    C -->|Extend| B\n    D(Import a list of connected objects) -->|Extend| B\n    E(Download a model) -->|Extend| B\n    F(Export the list of connected objects) -->|Extend| B\n    G(Delete a connected object) -->|Extend| B\n    H(Go to the details of a connected object) -->|Extend| B\n    I(Search for connected objects) -->|Extend| B
"},{"location":"dev-guide/conception/diagrams/#connected-object-models","title":"Connected object models","text":"
graph LR\n    A[End user] --> J(Display the list of connected object models)\n    A --> K(Add a connected object model)\n    K -->|Extend| J\n    L(Delete a connected object model) -->|Extend| J\n    M(Go to the details of a connected object model) -->|Extend| J
"},{"location":"dev-guide/conception/diagrams/#connected-object-configurations","title":"Connected object configurations","text":"
graph LR\n    A[End user] --> N(Display the list of connected object configurations)\n    A --> O(Add a connected object configuration)\n    O -->|Extend| N\n    P(Go to the details of a connected object configuration) --> |Extend| N
"},{"location":"dev-guide/conception/diagrams/#edge-connected-object-models","title":"Edge connected object models","text":"
graph LR\n    A[End user] --> Q(Display the list of Edge connected object models)\n    R(Add an Edge connected object model) -->|Extend| Q\n    S(Delete an Edge connected object model) -->|Extend| Q\n    T(Go to the details of a model of Edge connected object) -->|Extend| Q\n    U(Search for Edge connected object models) -->|Extend| Q
"},{"location":"dev-guide/conception/diagrams/#edge-connected-objects","title":"Edge connected objects","text":"
graph LR\n    A[End user] --> V(Display the list of Edge connected objects)\n    W(Add an Edge connected object) -->|Extend| V\n    X(Delete an Edge connected object) -->|Extend| V\n    Y(Go to the details of a connected object Edge) -->|Extend| V\n    Z(Search for Edge connected objects) -->|Extend| V
"},{"location":"dev-guide/conception/diagrams/#concentrators","title":"Concentrators","text":"
graph LR\n    A[End user] --> AA(Display the list of concentrators)\n    AB(Add a concentrator) -->|Extend| AA\n    AC(Delete a concentrator) -->|Extend| AA\n    AD(Go to the details of a concentrator) -->|Extend| AA
"},{"location":"dev-guide/conception/diagrams/#tags","title":"Tags","text":"
graph LR\n    A[End user] --> AE(Display the list of tags)\n    AF(Add a tag) -->|Extend| AE\n    AG(Delete a tag) -->|Extend| AE

Now, here is a diagram representing the multilayer technical architecture of the project.

C4Deployment\n    title Multilayer technical architecture\n\n    Deployment_Node(api, \"Server\", \"API\"){\n        Container(controllers, \"Controllers\", \"C#\", \"They are used to route HTTP requests, they call the methods of the services and they return the content of the HTTP response as well as a HTTP code.\")\n        Container(services, \"Services\", \"C#\", \"They are used to define the business logic as to call the methods of the data access layer for example.\")\n    }\n\n    Deployment_Node(bll, \"Application\", \"BLL\"){\n        Container(iservices, \"Services\", C#, \"This package represents the interfaces of the services.\")\n    }\n\n    Deployment_Node(dal, \"Infrastructure\", \"DAL\"){\n        Deployment_Node(uow, \"UnitOfWork\", \"UOW\"){\n            Container(repositories, \"Repositories\", \"C# and EntityFramework\", \"A repository represents all the data management methods of an entity of the project.\")\n        }\n    }\n\n    Deployment_Node(domain, \"Domain\", \"Domain\"){\n        Container(entities, \"Entities\", \"C#\", \"They are used as object representation of tables in a database.\")\n        Container(irepositories, \"Repositories\", \"C#\", \"This package represents the interfaces of the repositories.\")\n    }\n\n    Rel(iservices, services, \"dependency\", \"\")\n    Rel(repositories, services, \"dependency\", \"\")\n    Rel(entities, services, \"dependency\", \"\")\n    Rel(entities, iservices, \"dependency\", \"\")\n    Rel(iservices, repositories, \"dependency\", \"\")\n    Rel(entities, repositories, \"dependency\", \"\")

Now, to better understand the technical architecture of the project, here is a class diagram representing it.

classDiagram\n    direction LR\n    class AdminController{\n        -String value\n    }\n    class DashboardController{\n        -String value\n    }\n    class DeviceConfigurationsController{\n        -String value\n    }\n    class DeviceModelControllerBase{\n        -String value\n    }\n    class DeviceModelPropertiesController{\n        -String value\n    }\n    class DeviceModelPropertiesControllerBase{\n        -String value\n    }\n    class DeviceModelController{\n        -String value\n    }\n    class DevicesController{\n        -String value\n    }\n    class DevicesControllerBase{\n        -String value\n    }\n    class DeviceTagSettingsController{\n        -String value\n    }\n    class EdgeDevicesController{\n        -String value\n    }\n    class EdgeModelsController{\n        -String value\n    }\n    class IdeasController{\n        -String value\n    }\n    class SettingsController{\n        -String value\n    }\n    class LoRaWANCommandsController{\n        -String value\n    }\n    class LoRaWANConcentratorsController{\n        -String value\n    }\n    class LoRaWANDeviceModelsController{\n        -String value\n    }\n    class LoRaWANDevicesController{\n        -String value\n    }\n    class LoRaWANFrequencyPlansController{\n        -String value\n    }\n    LoRaWANDeviceModelsController --|> DeviceModelsControllerBase\n    LoRaWANDevicesController --|> DevicesControllerBase\n    DeviceModelPropertiesController --|> DeviceModelPropertiesControllerBase\n    DeviceModelsController --|> DeviceModelsControllerBase\n    DevicesController --|> DevicesControllerBase\n    class ConfigService{\n        -String value\n    }\n    class DeviceConfigurationsService{\n        -String value\n    }\n    class DeviceModelPropertiesService{\n        -String value\n    }\n    class DeviceModelService{\n        -String value\n    }\n    class DevicePropertyService{\n        -String value\n    }\n    class DeviceService{\n        -String value\n    }\n    class DeviceServiceBase{\n        -String value\n    }\n    class DeviceTagService{\n        -String value\n    }\n    class EdgeDevicesService{\n        -String value\n    }\n    class EdgeModelService{\n        -String value\n    }\n    class ExternalDeviceService{\n        -String value\n    }\n    class IdeaService{\n        -String value\n    }\n    class LoRaWANCommandService{\n        -String value\n    }\n    class LoRaWANConcentratorService{\n        -String value\n    }\n    class LoRaWanDeviceService{\n        -String value\n    }\n    class SubmitIdeaRequest{\n        -String value\n    }\n    DeviceService --|> DeviceServiceBase\n    LoRaWanDeviceService --|> DeviceServiceBase\n    class IConfigService\n    <<interface>> IConfigService\n    class IDeviceConfigurationsService\n    <<interface>> IDeviceConfigurationsService\n    class IDeviceModelPropertiesService\n    <<interface>> IDeviceModelPropertiesService\n    class IDeviceModelService\n    <<interface>> IDeviceModelService\n    class IDevicePropertyService\n    <<interface>> IDevicePropertyService\n    class IDeviceService\n    <<interface>> IDeviceService\n    class IDeviceTagService\n    <<interface>> IDeviceTagService\n    class IEdgeDevicesService\n    <<interface>> IEdgeDevicesService\n    class IEdgeModelService\n    <<interface>> IEdgeModelService\n    class IExternalDeviceService\n    <<interface>> IExternalDeviceService\n    class IIdeaService\n    <<interface>> IIdeaService\n    class ILoRaWANCommandService\n    <<interface>> ILoRaWANCommandService\n    class ILoRaWANConcentratorService\n    <<interface>> ILoRaWANConcentratorService\n    class ILoRaWanManagementService\n    <<interface>> ILoRaWanManagementService\n    ConfigService ..|> IConfigService\n    DeviceConfigurationsService ..|> IDeviceConfigurationsService\n    DeviceModelPropertiesService ..|> IDeviceModelPropertiesService\n    DeviceModelService ..|> IDeviceModelService\n    DevicePropertyService ..|> IDevicePropertyService\n    DeviceServiceBase ..|> IDeviceService\n    DeviceTagService ..|> IDeviceTagService\n    EdgeDevicesService ..|> IEdgeDevicesService\n    EdgeModelService ..|> IEdgeModelService\n    ExternalDeviceService ..|> IExternalDeviceService\n    IdeaService ..|> IIdeaService\n    LoRaWANCommandService ..|> ILoRaWANCommandService\n    LoRaWANConcentratorService ..|> ILoRaWANConcentratorService\n    class ConcentratorRepository{\n        -String value\n    }\n    class DeviceModelCommandRepository{\n        -String value\n    }\n    class DeviceModelPropertiesRepository{\n        -String value\n    }\n    class DeviceModelRepository{\n        -String value\n    }\n    class DeviceRepository{\n        -String value\n    }\n    class DeviceTagRepository{\n        -String value\n    }\n    class DeviceTagValueRepository{\n        -String value\n    }\n    class EdgeDeviceModelCommandRepository{\n        -String value\n    }\n    class EdgeDeviceModelRepository{\n        -String value\n    }\n    class EdgeDeviceRepository{\n        -String value\n    }\n    class GenericRepository{\n        -String value\n    }\n    class LabelRepository{\n        -String value\n    }\n    class LoRaDeviceTelemetryRepository{\n        -String value\n    }\n    class LorawanDeviceRepository{\n        -String value\n    }\n    class UnitOfWork{\n        -String value\n    }\n    class IConcentratorRepository\n    <<interface>> IConcentratorRepository\n    class IDeviceModelCommandRepository\n    <<interface>> IDeviceModelCommandRepository\n    class IDeviceModelPropertiesRepository\n    <<interface>> IDeviceModelPropertiesRepository\n    class IDeviceModelRepository\n    <<interface>> IDeviceModelRepository\n    class IDeviceRepository\n    <<interface>> IDeviceRepository\n    class IDeviceTagRepository\n    <<interface>> IDeviceTagRepository\n    class IDeviceTagValueRepository\n    <<interface>> IDeviceTagValueRepository\n    class IEdgeDeviceModelCommandRepository\n    <<interface>> IEdgeDeviceModelCommandRepository\n    class IEdgeDeviceModelRepository\n    <<interface>> IEdgeDeviceModelRepository\n    class IEdgeDeviceRepository\n    <<interface>> IEdgeDeviceRepository\n    class ILabelRepository\n    <<interface>> ILabelRepository\n    class ILoRaDeviceTelemetryRepository\n    <<interface>> ILoRaDeviceTelemetryRepository\n    class ILorawanDeviceRepository\n    <<interface>> ILorawanDeviceRepository\n    class IRepository\n    <<interface>> IRepository\n    class IUnitOfWork\n    <<interface>> IUnitOfWork\n    UnitOfWork ..|> IUnitOfWork\n    ConcentratorRepository ..|> IConcentratorRepository\n    ConcentratorRepository --|> GenericRepository\n    DeviceModelCommandRepository ..|> IDeviceModelCommandRepository\n    DeviceModelCommandRepository --|> GenericRepository\n    DeviceModelPropertiesRepository ..|> IDeviceModelPropertiesRepository\n    DeviceModelPropertiesRepository --|> GenericRepository\n    DeviceModelRepository ..|> IDeviceModelRepository\n    DeviceModelRepository --|> GenericRepository\n    DeviceRepository ..|> IDeviceRepository\n    DeviceRepository --|> GenericRepository\n    DeviceTagRepository ..|> IDeviceTagRepository\n    DeviceTagRepository --|> GenericRepository\n    DeviceTagValueRepository ..|> IDeviceTagValueRepository\n    DeviceTagValueRepository --|> GenericRepository\n    EdgeDeviceModelCommandRepository ..|> IEdgeDeviceModelCommandRepository\n    EdgeDeviceModelCommandRepository --|> GenericRepository\n    EdgeDeviceModelRepository ..|> IEdgeDeviceModelRepository\n    EdgeDeviceModelRepository --|> GenericRepository\n    EdgeDeviceRepository ..|> IEdgeDeviceRepository\n    EdgeDeviceRepository --|> GenericRepository\n    GenericRepository ..|> IRepository\n    LabelRepository ..|> ILabelRepository\n    LabelRepository --|> GenericRepository\n    LoRaDeviceTelemetryRepository ..|> ILoRaDeviceTelemetryRepository\n    LoRaDeviceTelemetryRepository --|> GenericRepository\n    LorawanDeviceRepository ..|> ILorawanDeviceRepository\n    LorawanDeviceRepository --|> GenericRepository
"},{"location":"dev-guide/migrations/v3-to-v4/","title":"Migrate from v3 to v4","text":"

To migrate from v3 to v4 manually, you have to add two new settings to the portal web app. These two settings are required to to pull devices telemetry from the IoT Hub:

Name Setting Type Detail IoTHub__EventHub__ConsumerGroup Application setting (Default value iothub-portal) The name of the consumer group used to to pull data from the IoT Hub IoTHub__EventHub__Endpoint Connection string The IotHub Event Hub compatible endpoint

Below the required steps for each settings:

"},{"location":"dev-guide/migrations/v3-to-v4/#iothub__eventhub__consumergroup","title":"IoTHub__EventHub__ConsumerGroup","text":"
  1. Go to your IoT Hub
  2. Navigate to menu Built-in endpoints
  3. Create a consumer group with the name iothub-portal
  4. Back to the portal web app, add a new application setting with name IoTHub__EventHub__ConsumerGroup and with value iothub-portal
"},{"location":"dev-guide/migrations/v3-to-v4/#iothub__eventhub__endpoint","title":"IoTHub__EventHub__Endpoint","text":"
  1. Go to your IoT Hub
  2. Navigate to menu Built-in endpoints
  3. On the section Event Hub compatible endpoint
    1. Select the shared access policy service
    2. Copy the value of the event Hub-compatible endpoint
  4. Back to the portal web app, add a new connection setting with name IoTHub__EventHub__Endpoint and with value the event Hub-compatible endpoint copied earlier

Info

You can create your own shared access policy. But the portal needs at least the Service Connect permission

"},{"location":"dev-guide/migrations/v4-to-v5/","title":"Migrate from v4 to v5","text":"

In this v5, the major change is the integration of AWS in the portal. Some changes have also been made at the portal web app settings.

"},{"location":"dev-guide/migrations/v4-to-v5/#aws","title":"AWS","text":"

Starting from version 5, the portal now supports AWS integration. To learn how to deploy AWS services using the portal, please refer to the Quick Start for AWS documentation. It provides step-by-step instructions on setting up and deploying AWS resources using the portal's interface.

"},{"location":"dev-guide/migrations/v4-to-v5/#azure","title":"Azure","text":"

To migrate from v4 to v5 manually, you have to add CloudProvider with Azure as default value. You have to add also Azure__ prefix in all setting to the portal web app.

Name Setting Type Detail CloudProvider Application setting (Possible value Azure) The name of the CLoud Provider to run in the portal Azure__LoRaRegionRouterConfig__Url Application setting The Url for LoRa Region Router Configuration Azure__LoRaKeyManagement__Url Application setting The Url for LoRa Key Management Azure__LoRaKeyManagement__Code Application setting The Code for LoRa Key Management Azure__LoRaFeature__Enabled Application setting To enable or disable LoRa Feature Azure__IoTHub__ConnectionString Connection string The IotHub Connection String Azure__IoTHub__EventHub__Endpoint Connection string The IotHub Event Hub compatible endpoint Azure__IoTHub__EventHub__ConsumerGroup Application setting (Default value iothub-portal) The name of the consumer group used to to pull data from the IoT Hub Azure__IoTDPS__ServiceEndpoint Application setting The IotDPS Service Endpoint Azure__IoTDP__LoRaEnrollmentGroup Application setting The name of the IotDPS LoRa Enrollment group Azure__IoTDPS__DefaultEnrollmentGroup Application setting The name of the default IotDPS Enrollment group Azure__IoTDPS__ConnectionString Connection string The IotDPS Connection String Azure__StorageAccount__ConnectionString Connection string The Storage Account Connection String"},{"location":"dev-guide/testing/unit-tests-common-practices/","title":"Unit Tests Common Practices","text":""},{"location":"dev-guide/testing/unit-tests-common-practices/#naming-conventions","title":"Naming Conventions","text":""},{"location":"dev-guide/testing/unit-tests-common-practices/#test-class","title":"Test class","text":"

The test class should follow the naming convention [ClassUnderTest]Tests.

Example: The test class for a class named ProductController should be named ProductControllerTests:

C#
[TestFixture]\npublic class ProductControllerTests\n{\n    ...\n}\n
"},{"location":"dev-guide/testing/unit-tests-common-practices/#test-method","title":"Test method","text":"

The test method should follow the naming convention [MethodUnderTest]_[BehaviourToTest]_[ExpectedResult].

Example: A method named GetProduct should be tested to see if it returns an existing product. The name of the test should be GetProduct_ProductExist_ProductReturned:

C#
[Test]\npublic async Task GetProduct_ProductExist_ProductReturned()\n{\n    ...\n}\n
"},{"location":"dev-guide/testing/unit-tests-common-practices/#unit-test-skeleton-three-stepsparts","title":"Unit Test Skeleton: Three Steps/Parts","text":"

A unit test should be devided into three steps:

  1. Arrange: The first part where the input/expected data are defined
  2. Act: The second part where the behavior under test is executed
  3. Assert: The third and final part where assertions are made

These three parts are visually defined with comments so that unit tests are humanly comprehensible:

C#
[Test]\npublic async Task GetProduct_ProductExist_ProductReturned()\n{\n    // Arrange\n    var productId = Guid.NewGuid().ToString();\n    var expectedProduct = new Product\n    {\n        Id = productId\n    };\n\n    // Act\n    var product = this.productService.GetProduct(productId);\n\n    // Asset\n    _ = product.Should().BeEquivalentTo(expectedProduct);\n}\n

Tip

On the IoT Hub portal, we use the fluentassertions library for unit tests for natural/human reusable assertions.

"},{"location":"dev-guide/testing/unit-tests-common-practices/#mock","title":"Mock","text":"

A unit test should only test its assigned layer. Any lower layer that requires/interacts with external resources should be mocked to ensure sure that the unit tests are idempotent.

Note

Example: We want to implement unit tests for a controller that requires three services. Each service depends on other services/repositories/http clients that need external resources like databases, APIs... Any execution of unit tests that depend on these external resources can be altered (not idempotent) because they depend on the uptime and data of these resources.

On the IoT Hub portal, we use the library Moq for mocking within unit tests:

C#
[TestFixture]\npublic class ProductControllerTests\n{\n    private MockRepository mockRepository;\n    private Mock<IProductRepository> mockProductRepository;\n\n    private IProductService productService;\n\n    [SetUp]\n    public void SetUp()\n    {\n        // Init MockRepository with strict behaviour\n        this.mockRepository = new MockRepository(MockBehavior.Strict);\n        // Init the mock of IProductRepository\n        this.mockProductRepository = this.mockRepository.Create<IProductRepository>();\n        // Init the service ProductService. The object mock ProductRepository is passed the contructor of ProductService\n        this.productService = new ProductService(this.mockProductRepository.Object);\n    }\n\n    [Test]\n    public async Task GetProduct_ProductExist_ProductReturned()\n    {\n        // Arrange\n        var productId = Guid.NewGuid().ToString();\n        var expectedProduct = new Product\n        {\n            Id = productId\n        };\n\n        // Setup mock of GetByIdAsync of the repository ProductRepository to return the expected product when given the correct product id\n        _ = this.mockProductRepository.Setup(repository => repository.GetByIdAsync(productId))\n                .ReturnsAsync(expectedProduct);\n\n        // Act\n        var product = this.productService.GetProduct(productId);\n\n        // Asset\n        _ = product.Should().BeEquivalentTo(expectedProduct);\n\n        // Assert that all mocks setups have been called\n        _ = MockRepository.VerifyAll();\n    }\n}\n
"},{"location":"dev-guide/testing/unit-tests-on-blazor-components/","title":"Unit Tests on Blazor components","text":"

Info

To test Blazor components on the Iot Hob Portal, we use the library bUnit

"},{"location":"dev-guide/testing/unit-tests-on-blazor-components/#how-to-unit-test-component","title":"How to unit test component","text":"

Let us assume we have a compoment ProductDetail to test.

Example of the content of the component ProductDetail
@inject IProductService ProductService\n\n@if(product != null)\n{\n    <p id=\"product-id\">@product.Id</p>\n}\n\n@code {\n    [Parameter]\n    public string ProductId { get; set; }\n\n    private Product product;\n\n    protected override async Task OnInitializedAsync()\n    {\n        await GetProduct();\n    }\n\n    private async Task GetProduct()\n    {\n        try\n        {\n            product = await ProductService.GetProduct(ProductId);\n        }\n        catch (ProblemDetailsException exception)\n        {\n            Error?.ProcessProblemDetails(exception);\n        }\n    }\n}\n
First you have to a unit test class that extend
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n}\n

Info

The class BlazorUnitTest provides helpers/test context dedicated for unit tests for the blazor component. It also avoids code duplication of unit test classes.

Override the method Setup
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n    public override void Setup()\n    {\n        // Don't forget the method base.Setup() to initialize existing helpers\n        base.Setup();\n    }\n}\n
Setup the mockup of the service IProductService
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n    // Declare the mock of IProductService\n    private Mock<IProductService> productServiceMock;\n\n    public override void Setup()\n    {\n        base.Setup();\n\n        // Intialize the mock of IProductService\n        this.productServiceMock = MockRepository.Create<IProductService>();\n\n        // Add the mock of IProductService as a singleton for resolution \n        _ = Services.AddSingleton(this.productServiceMock.Object);\n    }\n}\n

Info

After configuring the test class setup, you can start implementing unit tests.

Below is an example of a a unit test that checks whether the GetProduct method of the serivce ProductService service was called after the component was initialized:

C#
[TestFixture]\npublic class ProductDetailTests : BlazorUnitTest\n{\n    ...\n\n    [Test]\n    public void OnInitializedAsync_GetProduct_ProductIsRetrieved()\n    {\n        // Arrange\n        var expectedProduct = Fixture.Create<Product>();\n\n        // Setup mock of GetProduct of the service ProductService\n        _ = this.productServiceMock.Setup(service => service.GetProduct(expectedProduct.Id))\n            .ReturnsAsync(expectedProduct);\n\n        // Act\n        // Render the component ProductDetail with the required ProductId parameter\n        var cut = RenderComponent<ProductDetail>(ComponentParameter.CreateParameter(\"ProductId\", expectedProduct.Id));\n        // You can wait for a specific element to be rendered before assertions using a css selector, for example the DOM element with id product-id\n        _ = cut.WaitForElement(\"#product-id\");\n\n        // Assert\n        // Assert that all mocks setups have been called\n        cut.WaitForAssertion(() => MockRepository.VerifyAll());\n    }\n}\n

Tip

WaitForAssertion is useful in asserting asynchronous changes: It will blocks and waits in a test method until the specified assertion action does not throw an exception, or until the timeout is reached (the default timeout is one second). Assertion of asynchronous changes

Tip

Within unit tests on Blazor components, you can interact with HTML DOM and query rendered HTMLelements (buttons, div...) by using CSS selectors (id, class...) Lean more about CSS selectors

"},{"location":"dev-guide/testing/unit-tests-on-blazor-components/#how-to-unit-test-a-component-requiring-an-external-component","title":"How to unit test a component requiring an external component","text":"

Some components proposed by MudBlazor (MudAutocomplete, MudSelect...) use another component MudPopoverProvider to display elements. If in a unit test that uses these MudBlazor components, the MudPopoverProvider component is not rendered, the interactions with these components are restricted.

Let us start with the following example:

Example of the content of the component SearchState
<MudAutocomplete T=\"string\" Label=\"US States\" @bind-Value=\"selectedState\" SearchFunc=\"@Search\" />\n\n@code {\n    private string selectedState;\n    private string[] states =\n    {\n        \"Alabama\", \"Colorado\", \"Missouri\", \"Wisconsin\"\n    }\n\n    private async Task<IEnumerable<string>> Search(string value)\n    {\n        // In real life use an asynchronous function for fetching data from an api.\n        await Task.Delay(5);\n\n        // if text is null or empty, show complete list\n        if (string.IsNullOrEmpty(value)) \n            return states;\n        return states.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase));\n    }\n}\n

We want to test the search when a user interacts with the MudAutocomplete component to search for the state Wisconsin:

C#
[TestFixture]\npublic class SearchStateTests : BlazorUnitTest\n{\n    ...\n\n    [Test]\n    public void Search_UserSearchAndSelectState_StateIsSelected()\n    {\n        // Arrange\n        var userQuery = \"Wis\";\n\n        // First render MudPopoverProvider component\n        var popoverProvider = RenderComponent<MudPopoverProvider>();\n        // Second, rendrer the component SearchState (under unit test)\n        var cut = RenderComponent<SearchState>();\n\n        // Find the MudAutocomplete component within SearchState component\n        var autocompleteComponent = cut.FindComponent<MudAutocomplete<string>>();\n\n        // Fire click event on, \n        autocompleteComponent.Find(\"input\").Click();\n        autocompleteComponent.Find(\"input\").Input(userQuery);\n\n        // Wait until the count of element in the list rendred on the component MudPopoverProvider is equals to one\n        popoverProvider.WaitForAssertion(() => popoverProvider.FindAll(\"div.mud-list-item\").Count.Should().Be(1));\n\n        // Act\n        // Get the only element present on the list\n        var stateElement = popoverProvider.Find(\"div.mud-list-item\");\n        // Fire click event on the element\n        stateElement.Click();\n\n        // Assert\n        // Check if the MudAutocomplete compoment has been closed after the click event\n        cut.WaitForAssertion(() => autocompleteComponent.Instance.IsOpen.Should().BeFalse());\n        ...\n    }\n}\n
"}]} \ No newline at end of file diff --git a/dev/sitemap.xml.gz b/dev/sitemap.xml.gz index 3d6eb4fa6..ca4f83970 100644 Binary files a/dev/sitemap.xml.gz and b/dev/sitemap.xml.gz differ