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 @@ - +
Azure__StorageAccount__ConnectionString
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":"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":"This schema represent the various components and how they interact to have a better understanding of the various solution elements.
You must enable Fleet Indexing for registry and shadow Using AWS CLI:
Bash Sessionaws iot update-indexing-configuration --thing-indexing-configuration thingIndexingMode=REGISTRY_AND_SHADOW\n
Please note that you'll need the necessary permissions to execute this command. Make sure your AWS IAM user or role has the appropriate permissions to access and modify the IoT indexing configuration.
The template will deploy in your AWS Account the Following resources:
Choose a stack name for your AWS Deployment.
Follow next step below to start your deployment:
Press on the button here below to download the template AWS: Download the template
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 Sessionaws 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
You will get to a page asking you to fill the following fields:
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.
{\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.
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":"The template will deploy in your Azure subscription the Following resources:
Choose a solution prefix for your Azure Deployment.
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:
<your-openid-authority>
<your-openid-provider-metadata-url>
<your-client-id>
<your-client-id>
Press on the button here below to start your deployment on Azure:
You will get to a page asking you to fill the following fields:
see: https://azure.github.io/iotedge-lorawan-starterkit/dev/quickstart/#deployed-azure-infrastructure for more information about the LoRaWan IoT Hub and Azure deployment.
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 :
{\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":"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)
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:
https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/
.<tenant-name>
with your actual tenant name.https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=<policy-name>
.<tenant-name>
and <policy-name>
with your actual tenant name and the policy name you are using (like B2C_1_SignUpSignIn).Configure the requiered AD Applications.
Create the IoT Hub Portal API Application:
User.Invite.All
User.ManageIdentities.All
User.Read.All
User.ReadWrite.All
API.Access
(this is the name of the scope that will be used in the template)Access to the Portal API
Allows the application to get access to the Portal API
Create the IoT Hub Portal Client Application:
Configure the required User flow:
The Azure IoT Hub portal inherits from Azure IoT Hub concepts to manage IoT devices.
It relies on the following concepts:
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":"Device Model Id
: The ID of the device model. > Note: Since the device model is shared among all the devices, theID should be unique. For convenience, the ID is generated by the application. By using the API, the ID is not required, but if provided, it will be used as the ID of the device model.Name
: The name of the device model.Description
: The description of the device model.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":"Device Id
: The ID of the device. > Note: It is the device id stored in the Azure IoT Hub. It is asked to the user when creating a device and is not editable after the device is created.Name
: The name of the device model. > Note: The device name is the device friendly name. The name is editable after the device is created.Device Model
: The device model that the device is based on. > Note: The device model is asked to the user during the device creation and is not editable after the device is created.Status
: The status of the device. > Note: The status is related to the Device status in the Azure IoT Hub.Tags
: The tags of the device. > Note: The tags are related to the Device tags in the Azure IoT Hub. They could be defined at the portal level and set to the device for filtering and targeting for configuration.To store additional information about the device, the application uses device twin tags.
Name Position Description deviceNametags.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":"Type
: The type of the IoT Edge device. > Note: related to the IoT Edge purpose tag value that might be used to create deployment manifests.Environment
: The IoT Edge device environment (Development, Production, QA). > Note: this is an additional field that can be used to create deployment manifests.Status
: The status of the device. > Note: The status is related to the Device status in the Azure IoT Hub.Nbr of connected devices
: The number of devices connected to the IoT Edge device. > Note: The number of connected devices is related to the number of connections that are currently present in the edgeHub module. This might be different from the number of devices connected to the IoT Edge device if some modules are using edgeHub connections.Nbr of desired modules
: The number of modules that are desired in the last deployment.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 Environmenttags.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.
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 Ownertags.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.
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.
Support OTAA/ABP setting
: The device model supports OTAA/ABP connectivity.Type
: The LoRaWAN device class type (A or C).Message Deduplication
: Allows controlling the handling of duplicate messages received by multiple gateways.The default is Drop.OTAA AppEUI
: The device model OTAA App EUI used for the device during the OTAA Join procedure.Sensor Decoder URL
: The Sensor Decoder URL that the network server should use to decode frames comming from the devices that inherit from this model.Device Connection Timeout
: Allows defining a sliding expiration to the connection between the leaf device and IoT/Edge Hub. The default is none, which causes the connection to not be dropped.Support downstream messages
: Allows controlling the support of downstream messages. The default is false.Preferred receive window
: Allows setting the device preferred receive window (RX1 or RX2). The default preferred receive window is 1RX Delay
: Allows setting a custom wait time between receiving and transmission as specified in the specification.RX1 Data offset
: Allows setting a custom data offset for the RX1 receive window. The default is 0.RX2 Data rate
: Allows setting a custom data rate for the RX2 receive window. The default is 0.32bit counter support
: Allow the usage of 32bit counters on your device.Frame counter up start value
: Allows setting the frame counter start value for upstream messages. The default is 0.Frame counter down start value
: Allows setting the frame counter start value for downstream messages. The default is 0.Frame counter reset value
: Allows to reset the frame counters to the FCntUpStart/FCntDownStart values respectively.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.
Name
: The command name. This name is only a friendly name that the user can set to understand what the command is supposed to do.Frame
: The LoRaWAN frame (in hex) to be sent to the device.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":"Device ID
: The Station EUI for the LoRaWan Basic station.Device Name
: The friendly name for the device.Client Certificate Thumbprint
: The client certificate thumbprint used by the Basic Station to authenticate to the LoRaWAN Network Server.Region
: The LoRaWAN region used by the Basic Station (EU868, US915, AS923 and CN470 supported).To store additional information about the concentrator, the application will use the target device to extract values:
Name Position Description Device Nametags.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 resultSee 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:
This schema represent the various components and how they interact to have a better understanding of the various solution elements.
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.
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 :
{\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:
.well-known/openid-configuration
).iothub-portal
) The name of the consumer group used to to pull data from the IoT Hub (Automatically created by the Bicep/ARM deployement)Development
: On this environment, logs are produced up to Debug
level.Production
: Default value if ASPNETCORE_ENVIRONMENT is not set. On this environment, logs are produced up to Information
level.30
) The refresh interval in seconds
to collect custom metrics and expose them to the exporter endpoint.10
) The refresh interval in minutes
to calculate/refresh custom metrics values.false
) To enable Ideas feature when set to true
.Awesome-Ideas
, to publish ideas submitted by users.Ocp-Apim-Subscription-Key
) Authentication header name.5
) The refresh interval in minutes
to collect data from Azure IoT Hub (Devices, Iot Edge Devices...) and store them on the database of the Portal.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.
This boolean adds the following headers to all responses :
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
- only applied to HTTPS responses
X-Frame-Options: Deny
- only applied to text/html responses
X-XSS-Protection: 1; mode=block
- only applied to text/html responses
Referrer-Policy: strict-origin-when-cross-origin
- only applied to text/html responses
Content-Security-Policy: object-src 'none'; form-action 'self'; frame-ancestors 'none'
- only applied to text/html responses.
The default is true.
Validation of the issuer mitigates forwarding attacks that can occur when an IdentityProvider represents multiple tenants and signs tokens with the same keys. It is possible that a token issued for the same audience could be from a different tenant. For example an application could accept users from contoso.onmicrosoft.com but not fabrikam.onmicrosoft.com, both valid tenants. An application that accepts tokens from fabrikam could forward them to the application that accepts tokens for contoso. This boolean only applies to default issuer validation. If IssuerValidator is set, it will be called regardless of whether this property is true or false.
The default is true.
Validation of the audience, mitigates forwarding attacks. For example, a site that receives a token, could not replay it to another side. A forwarded token would contain the audience of the original site. This boolean only applies to default audience validation. If AudienceValidator is set, it will be called regardless of whether this property is true or false.
The default is true.
This boolean only applies to default lifetime validation. If LifetimeValidator is set, it will be called regardless of whether this property is true or false.
The default is true.
It is possible for tokens to contain the public key needed to check the signature. For example, X509Data can be hydrated into an X509Certificate, which can be used to validate the signature. In these cases it is important to validate the SigningKey that was used to validate the signature. This boolean only applies to default signing key validation. If IssuerSigningKeyValidator is set, it will be called regardless of whether this property is true or false.
The default is false.
If an actor token is detected, whether it should be validated.
The default is false.
This boolean only applies to default token replay validation. If TokenReplayValidator is set, it will be called regardless of whether this property is true or false.
The default is false.
The IoT Hub portal uses some tags to configure the devices. The tags are stored in the Azure IoT Hub in Device Twins.
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:
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.
Checkout the branch that contains the documentation:
Bash Sessiongit checkout origin/docs/main\ngit checkout -b docs/<your_branch_name> \n
Install dependencies
Bash Sessionpip install -r requirements.txt\n
Previewing as you write
Bash Sessionmkdocs serve\n
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
Refer to Material for MkDocs documentations:
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 usingProblem Details
","text":"BaseException
. For example see \ud83d\udc49 InternalServerErrorException
services.AddProblemDetails()
: > Your new exception is already catched by the middleware Problem Details because its extends the exception BaseException
. > If you want override the behavior of the middleware when processing your exception, you have to add a new mapping within it.\ud83d\udca1 You can also map exceptions from dotnet framework and third parties.
"},{"location":"dev-guide/#handle-problem-details-exceptions-on-frontend","title":"HandleProblem Details
exceptions on frontend","text":"On frontend, http client uses a delegating handler ProblemDetailsHandler to:
ProblemDetailsWithExceptionDetails
ProblemDetailsException
(including the error response) is thrown.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:
Error
component, so that it can visually warn the user@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:
Open the terminal and run this command:
Bash Sessiondotnet tool install --global dotnet-ef\n
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:
Go into the Server project folder with terminal
Bash Sessioncd .\\IoTHub.Portal.Server\\\n
Execute this command for PostgreSQL provider
Bash Sessiondotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.Postgres\\ -v -- --DbProvider PostgreSQL\n
Execute this command for MySQL provider
Bash Sessiondotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.MySql\\ -v -- --DbProvider MySQL\n
Open the created migration and follow the following steps:
Move the using directive into the namespace directive
Add \"_ =\" before each statement of the Up and Down methods
Add the CGI copyright to the top of the file
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:
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":"iothub-portal
IoTHub__EventHub__ConsumerGroup
and with value iothub-portal
IoTHub__EventHub__Endpoint
","text":"service
IoTHub__EventHub__Endpoint
and with value the event Hub-compatible endpoint copied earlierInfo
You can create your own shared access policy. But the portal needs at least the Service Connect
permission
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.
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.
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
:
[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
:
[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:
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.
@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.
[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
:
[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":"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":"This schema represent the various components and how they interact to have a better understanding of the various solution elements.
You must enable Fleet Indexing for registry and shadow Using AWS CLI:
Bash Sessionaws iot update-indexing-configuration --thing-indexing-configuration thingIndexingMode=REGISTRY_AND_SHADOW\n
Please note that you'll need the necessary permissions to execute this command. Make sure your AWS IAM user or role has the appropriate permissions to access and modify the IoT indexing configuration.
The template will deploy in your AWS Account the Following resources:
Choose a stack name for your AWS Deployment.
Follow next step below to start your deployment:
Press on the button here below to download the template AWS: Download the template
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 Sessionaws 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
You will get to a page asking you to fill the following fields:
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.
{\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.
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":"The template will deploy in your Azure subscription the Following resources:
Choose a solution prefix for your Azure Deployment.
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:
<your-openid-authority>
<your-openid-provider-metadata-url>
<your-client-id>
<your-client-id>
Press on the button here below to start your deployment on Azure:
You will get to a page asking you to fill the following fields:
see: https://azure.github.io/iotedge-lorawan-starterkit/dev/quickstart/#deployed-azure-infrastructure for more information about the LoRaWan IoT Hub and Azure deployment.
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 :
{\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":"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)
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:
https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/
.<tenant-name>
with your actual tenant name.https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=<policy-name>
.<tenant-name>
and <policy-name>
with your actual tenant name and the policy name you are using (like B2C_1_SignUpSignIn).Configure the requiered AD Applications.
Create the IoT Hub Portal API Application:
User.Invite.All
User.ManageIdentities.All
User.Read.All
User.ReadWrite.All
API.Access
(this is the name of the scope that will be used in the template)Access to the Portal API
Allows the application to get access to the Portal API
Create the IoT Hub Portal Client Application:
Configure the required User flow:
The Azure IoT Hub portal inherits from Azure IoT Hub concepts to manage IoT devices.
It relies on the following concepts:
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":"Device Model Id
: The ID of the device model. > Note: Since the device model is shared among all the devices, theID should be unique. For convenience, the ID is generated by the application. By using the API, the ID is not required, but if provided, it will be used as the ID of the device model.Name
: The name of the device model.Description
: The description of the device model.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":"Device Id
: The ID of the device. > Note: It is the device id stored in the Azure IoT Hub. It is asked to the user when creating a device and is not editable after the device is created.Name
: The name of the device model. > Note: The device name is the device friendly name. The name is editable after the device is created.Device Model
: The device model that the device is based on. > Note: The device model is asked to the user during the device creation and is not editable after the device is created.Status
: The status of the device. > Note: The status is related to the Device status in the Azure IoT Hub.Tags
: The tags of the device. > Note: The tags are related to the Device tags in the Azure IoT Hub. They could be defined at the portal level and set to the device for filtering and targeting for configuration.To store additional information about the device, the application uses device twin tags.
Name Position Description deviceNametags.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":"Type
: The type of the IoT Edge device. > Note: related to the IoT Edge purpose tag value that might be used to create deployment manifests.Environment
: The IoT Edge device environment (Development, Production, QA). > Note: this is an additional field that can be used to create deployment manifests.Status
: The status of the device. > Note: The status is related to the Device status in the Azure IoT Hub.Nbr of connected devices
: The number of devices connected to the IoT Edge device. > Note: The number of connected devices is related to the number of connections that are currently present in the edgeHub module. This might be different from the number of devices connected to the IoT Edge device if some modules are using edgeHub connections.Nbr of desired modules
: The number of modules that are desired in the last deployment.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 Environmenttags.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.
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 Ownertags.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.
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.
Support OTAA/ABP setting
: The device model supports OTAA/ABP connectivity.Type
: The LoRaWAN device class type (A or C).Message Deduplication
: Allows controlling the handling of duplicate messages received by multiple gateways.The default is Drop.OTAA AppEUI
: The device model OTAA App EUI used for the device during the OTAA Join procedure.Sensor Decoder URL
: The Sensor Decoder URL that the network server should use to decode frames comming from the devices that inherit from this model.Device Connection Timeout
: Allows defining a sliding expiration to the connection between the leaf device and IoT/Edge Hub. The default is none, which causes the connection to not be dropped.Support downstream messages
: Allows controlling the support of downstream messages. The default is false.Preferred receive window
: Allows setting the device preferred receive window (RX1 or RX2). The default preferred receive window is 1RX Delay
: Allows setting a custom wait time between receiving and transmission as specified in the specification.RX1 Data offset
: Allows setting a custom data offset for the RX1 receive window. The default is 0.RX2 Data rate
: Allows setting a custom data rate for the RX2 receive window. The default is 0.32bit counter support
: Allow the usage of 32bit counters on your device.Frame counter up start value
: Allows setting the frame counter start value for upstream messages. The default is 0.Frame counter down start value
: Allows setting the frame counter start value for downstream messages. The default is 0.Frame counter reset value
: Allows to reset the frame counters to the FCntUpStart/FCntDownStart values respectively.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.
Name
: The command name. This name is only a friendly name that the user can set to understand what the command is supposed to do.Frame
: The LoRaWAN frame (in hex) to be sent to the device.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":"Device ID
: The Station EUI for the LoRaWan Basic station.Device Name
: The friendly name for the device.Client Certificate Thumbprint
: The client certificate thumbprint used by the Basic Station to authenticate to the LoRaWAN Network Server.Region
: The LoRaWAN region used by the Basic Station (EU868, US915, AS923 and CN470 supported).To store additional information about the concentrator, the application will use the target device to extract values:
Name Position Description Device Nametags.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 resultSee 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:
This schema represent the various components and how they interact to have a better understanding of the various solution elements.
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.
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 :
{\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:
.well-known/openid-configuration
).iothub-portal
) The name of the consumer group used to to pull data from the IoT Hub (Automatically created by the Bicep/ARM deployement)Development
: On this environment, logs are produced up to Debug
level.Production
: Default value if ASPNETCORE_ENVIRONMENT is not set. On this environment, logs are produced up to Information
level.30
) The refresh interval in seconds
to collect custom metrics and expose them to the exporter endpoint.10
) The refresh interval in minutes
to calculate/refresh custom metrics values.false
) To enable Ideas feature when set to true
.Awesome-Ideas
, to publish ideas submitted by users.Ocp-Apim-Subscription-Key
) Authentication header name.5
) The refresh interval in minutes
to collect data from Azure IoT Hub (Devices, Iot Edge Devices...) and store them on the database of the Portal.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.
This boolean adds the following headers to all responses :
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
- only applied to HTTPS responses
X-Frame-Options: Deny
- only applied to text/html responses
X-XSS-Protection: 1; mode=block
- only applied to text/html responses
Referrer-Policy: strict-origin-when-cross-origin
- only applied to text/html responses
Content-Security-Policy: object-src 'none'; form-action 'self'; frame-ancestors 'none'
- only applied to text/html responses.
The default is true.
Validation of the issuer mitigates forwarding attacks that can occur when an IdentityProvider represents multiple tenants and signs tokens with the same keys. It is possible that a token issued for the same audience could be from a different tenant. For example an application could accept users from contoso.onmicrosoft.com but not fabrikam.onmicrosoft.com, both valid tenants. An application that accepts tokens from fabrikam could forward them to the application that accepts tokens for contoso. This boolean only applies to default issuer validation. If IssuerValidator is set, it will be called regardless of whether this property is true or false.
The default is true.
Validation of the audience, mitigates forwarding attacks. For example, a site that receives a token, could not replay it to another side. A forwarded token would contain the audience of the original site. This boolean only applies to default audience validation. If AudienceValidator is set, it will be called regardless of whether this property is true or false.
The default is true.
This boolean only applies to default lifetime validation. If LifetimeValidator is set, it will be called regardless of whether this property is true or false.
The default is true.
It is possible for tokens to contain the public key needed to check the signature. For example, X509Data can be hydrated into an X509Certificate, which can be used to validate the signature. In these cases it is important to validate the SigningKey that was used to validate the signature. This boolean only applies to default signing key validation. If IssuerSigningKeyValidator is set, it will be called regardless of whether this property is true or false.
The default is false.
If an actor token is detected, whether it should be validated.
The default is false.
This boolean only applies to default token replay validation. If TokenReplayValidator is set, it will be called regardless of whether this property is true or false.
The default is false.
The IoT Hub portal uses some tags to configure the devices. The tags are stored in the Azure IoT Hub in Device Twins.
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:
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.
Checkout the branch that contains the documentation:
Bash Sessiongit checkout origin/docs/main\ngit checkout -b docs/<your_branch_name> \n
Install dependencies
Bash Sessionpip install -r requirements.txt\n
Previewing as you write
Bash Sessionmkdocs serve\n
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
Refer to Material for MkDocs documentations:
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 usingProblem Details
","text":"BaseException
. For example see \ud83d\udc49 InternalServerErrorException
services.AddProblemDetails()
: > Your new exception is already catched by the middleware Problem Details because its extends the exception BaseException
. > If you want override the behavior of the middleware when processing your exception, you have to add a new mapping within it.\ud83d\udca1 You can also map exceptions from dotnet framework and third parties.
"},{"location":"dev-guide/#handle-problem-details-exceptions-on-frontend","title":"HandleProblem Details
exceptions on frontend","text":"On frontend, http client uses a delegating handler ProblemDetailsHandler to:
ProblemDetailsWithExceptionDetails
ProblemDetailsException
(including the error response) is thrown.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:
Error
component, so that it can visually warn the user@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:
Open the terminal and run this command:
Bash Sessiondotnet tool install --global dotnet-ef\n
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:
Go into the Server project folder with terminal
Bash Sessioncd .\\IoTHub.Portal.Server\\\n
Execute this command for PostgreSQL provider
Bash Sessiondotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.Postgres\\ -v -- --DbProvider PostgreSQL\n
Execute this command for MySQL provider
Bash Sessiondotnet ef migrations add \"<nameofyourmigration>\" -p ..\\IoTHub.Portal.MySql\\ -v -- --DbProvider MySQL\n
Open the created migration and follow the following steps:
Move the using directive into the namespace directive
Add \"_ =\" before each statement of the Up and Down methods
Add the CGI copyright to the top of the file
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:
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":"iothub-portal
IoTHub__EventHub__ConsumerGroup
and with value iothub-portal
IoTHub__EventHub__Endpoint
","text":"service
IoTHub__EventHub__Endpoint
and with value the event Hub-compatible endpoint copied earlierInfo
You can create your own shared access policy. But the portal needs at least the Service Connect
permission
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.
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.
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
:
[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
:
[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:
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.
@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.
[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
:
[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