├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── device-connectivity
├── .vscode
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── LICENSE.md
├── README.md
├── THIRD-PARTY-NOTICES.txt
├── appsettings.json
├── deviceConnectivity.csproj
├── models
│ └── CustomTelemetryMessage.cs
└── program.cs
├── digital-twins-samples.code-workspace
└── occupancy-quickstart
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE.md
├── README.md
├── docs
└── faq.md
├── src
├── actions
│ ├── actionNames.cs
│ ├── createEndpoints.cs
│ ├── createEndpoints.yaml
│ ├── createRoleAssignments.cs
│ ├── createRoleAssignments.yaml
│ ├── descriptions.cs
│ ├── descriptionsExtensions.cs
│ ├── getAvailableAndFreshSpaces.cs
│ ├── getCreationSummary.cs
│ ├── getSpaces.cs
│ ├── provisionResults.cs
│ ├── provisionSample.cs
│ ├── provisionSample.yaml
│ └── userDefinedFunctions
│ │ ├── availability.js
│ │ ├── availabilityForTutorial.js
│ │ └── multiplemotionsensors.js
├── api
│ ├── README.md
│ ├── create.cs
│ ├── find.cs
│ ├── get.cs
│ ├── isResourceProvisioning.cs
│ └── update.cs
├── appSettings.cs
├── appSettings.json
├── auth.cs
├── loggers.cs
├── loggingHttpHandler.cs
├── models
│ ├── README.md
│ ├── device.cs
│ ├── deviceCreate.cs
│ ├── endpointsCreate.cs
│ ├── historicalValues.cs
│ ├── keyStoreCreate.cs
│ ├── matcher.cs
│ ├── matcherCreate.cs
│ ├── ontology.cs
│ ├── property.cs
│ ├── propertyCreate.cs
│ ├── propertyKeyCreate.cs
│ ├── propertyKeys.cs
│ ├── resource.cs
│ ├── resourceCreate.cs
│ ├── roleAssignmentCreate.cs
│ ├── sensor.cs
│ ├── sensorCreate.cs
│ ├── sensorValues.cs
│ ├── space.cs
│ ├── spaceCreate.cs
│ ├── userDefinedFunction.cs
│ ├── userDefinedFunctionCreate.cs
│ └── userDefinedFunctionUpdate.cs
├── occupancyQuickstart.csproj
└── program.cs
└── tests
├── createEndpointsTests.cs
├── createRoleAssignmentsTests.cs
├── fakeDigitalTwinsHttpClient.cs
├── fakeHttpHandler.cs
├── loggers.cs
├── occupancyQuickstart.tests.csproj
├── provisionSampleDevicesTests.cs
├── provisionSampleMatchersTests.cs
├── provisionSampleResourcesTests.cs
├── provisionSampleRoleAssignmentsTests.cs
├── provisionSampleSensorsTests.cs
├── provisionSampleSpacesTests.cs
├── provisionSampleUserDefinedFunctionsTests.cs
├── responses.cs
└── userDefinedFunctions
├── function1.js
└── function2.js
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 | Please provide a succinct description of your issue. Please include which sample (ie top level folder) this is referring to.
4 |
5 | ### Repro steps
6 |
7 | Please provide the steps required to reproduce the problem.
8 |
9 | 1. Step A
10 |
11 | 2. Step B
12 |
13 | ### Expected behavior
14 |
15 | Please provide a description of the behavior you expect.
16 |
17 | ### Actual behavior
18 |
19 | Please provide a description of the actual behavior you observe.
20 |
21 | ### Logs
22 |
23 | If applicable, please provide any logs or output from the sample.
24 |
25 | ### Known workarounds
26 |
27 | Please provide a description of any known workarounds.
28 |
29 | ### Related information
30 |
31 | * Operating system
32 | * CoreCLR version (run `dotnet --version`)
33 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Purpose
2 |
3 | Please describe the intention of the changes being proposed. What problem does it solve or functionality does it add?
4 |
5 | ## Pull Request Type
6 |
7 | What kind of change does this Pull Request introduce?
8 |
9 |
10 | ```
11 | [ ] Bugfix
12 | [ ] New sample or new feature within a sample
13 | [ ] Code style update (formatting, naming)
14 | [ ] Refactoring (no functional changes)
15 | [ ] Documentation content changes
16 | [ ] Other... Please describe:
17 | ```
18 |
19 | ## Test By
20 |
21 |
28 |
29 | Please describe how you tested
30 |
31 | ## Other Information
32 |
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # Build results
13 | [Dd]ebug/
14 | [Dd]ebugPublic/
15 | [Rr]elease/
16 | [Rr]eleases/
17 | x64/
18 | x86/
19 | bld/
20 | [Bb]in/
21 | [Oo]bj/
22 | [Ll]og/
23 |
24 | # Visual Studio 2015/2017 cache/options directory
25 | .vs/
26 |
27 | # Visual Studio 2017 auto generated files
28 | Generated\ Files/
29 |
30 | # .NET Core
31 | project.lock.json
32 | project.fragment.lock.json
33 | artifacts/
34 | **/Properties/launchSettings.json
35 |
36 | # NuGet Packages
37 | *.nupkg
38 | # The packages folder can be ignored because of Package Restore
39 | **/[Pp]ackages/*
40 | # except build/, which is used as an MSBuild target.
41 | !**/[Pp]ackages/build/
42 | # Uncomment if necessary however generally it will be regenerated when needed
43 | #!**/[Pp]ackages/repositories.config
44 | # NuGet v3's project.json files produces more ignorable files
45 | *.nuget.props
46 | *.nuget.targets
47 |
48 | # Visual Studio cache files
49 | # files ending in .cache can be ignored
50 | *.[Cc]ache
51 | # but keep track of directories ending in .cache
52 | !*.[Cc]ache/
53 |
54 | # OSX
55 | .DS_Store
56 |
57 | # Others
58 | ClientBin/
59 | ~$*
60 | *~
61 | *.dbmdl
62 | *.dbproj.schemaview
63 | *.jfm
64 | *.pfx
65 | *.publishsettings
66 | orleans.codegen.cs
67 | appsettings.dev.json
68 | .accesstoken
69 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
5 | the rights to use your contribution. For details, visit https://cla.microsoft.com.
6 |
7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
9 | provided by the bot. You will only need to do this once across all repos using our CLA.
10 |
11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
14 |
15 | - [Ask a Question](#askaquestion)
16 | - [File a Bug](#bug)
17 | - [Request a Feature](#feature)
18 | - [Contribute Changes](#contribute)
19 |
20 | ## Ask a question
21 | We monitor the Github issues section specifically for bugs found with our samples, however we will reply to questions asked using Github issues too.
22 |
23 | The Azure Digital Twins team monitors Stack Overflow via the [azure-digital-twins](http://stackoverflow.com/questions/tagged/azure-digital-twins) tag. It is a great place to ask questions about [Azure Digital Twins](https://azure.microsoft.com/en-us/services/digital-twins/).
24 |
25 | ## File a bug (code or documentation)
26 |
27 | Bugs are definitely something we want to hear about! Please [open an issue](https://github.com/Azure-Samples/digital-twins-samples-csharp/issues/new) on github and we'll try address it as fast as possible. When creating the issue, please help us by filling out as much of the requested info as possible.
28 |
29 | Our samples are entirely open-source and we do accept pull-requests if you feel like taking a stab at fixing the bug. Please be sure to file an issues first and mention the relevant issue number in your pull request description.
30 |
31 | ## Request a Feature
32 | You can *request* a new feature by [submitting an issue](https://github.com/Azure-Samples/digital-twins-samples-csharp/issues/new) to the GitHub Repository. If you would like to *implement* a new feature, please submit an issue with
33 | a proposal for your work first, to be sure that we can use it.
34 |
35 | ## Contribute Changes
36 |
37 | We try to maintain a high bar for code quality and maintainability, we request that all new code samples are accompanied by documentation, example scenarios, and tests (for non trivial samples).
38 |
39 | If you feel like your contribution is going to be a major effort, before you spend the time, just check with us to make sure your plans and ours are in sync. Just [open an issue](https://github.com/Azure-Samples/digital-twins-samples-csharp/issues/new) on github and tag it "enhancement" or "feature request"
40 |
41 | Thank you for your contributions!
42 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | languages:
4 | - csharp
5 | products:
6 | - azure
7 | - azure-digital-twins
8 | name: Digital Twins Samples (ARCHIVED)
9 | description: "For the older version of Azure Digital Twins: This repo contains .NET Core samples that demonstrate how to use the Azure Digital Twins platform. Each folder contains a separate .NET Core app."
10 | ---
11 |
12 | # Digital Twins Samples (ARCHIVED)
13 |
14 | [](https://opensource.org/licenses/MIT)
15 |
16 | **************************************************
17 | **NOTE (July 2020): A new version of the Azure Digital Twins service has been released, with new features and new implementation details. This sample repository corresponds to the previous set of documentation, which has now been [archived](https://docs.microsoft.com/previous-versions/azure/digital-twins/about-digital-twins). It no longer reflects the current Azure Digital Twins best practices and is no longer being maintained.**
18 |
19 | **To view the latest information for the new service, visit the active [Azure Digital Twins Documentation](https://docs.microsoft.com/azure/digital-twins/) and its [samples](https://docs.microsoft.com/samples/azure-samples/digital-twins-samples/digital-twins-samples/).**
20 | **************************************************
21 |
22 | This repo contains .NET Core samples that demonstrate how to use the Azure Digital Twins platform. Each folder contains a separate .NET Core app.
23 |
24 | See the `README.md` in each sub-folder for specific details about each app:
25 |
26 | * [Occupancy sample](https://github.com/Azure-Samples/digital-twins-samples-csharp/tree/master/occupancy-quickstart/README.md)
27 | * [Device Connectivity sample](https://github.com/Azure-Samples/digital-twins-samples-csharp/tree/master/device-connectivity/README.md)
28 |
29 | ## Get Started
30 |
31 | 1. [Install dotnet core](https://www.microsoft.com/net/download).
32 | 1. Clone the repo:
33 |
34 | ```shell
35 | git clone https://github.com/Azure-Samples/digital-twins-samples-csharp.git
36 | cd digital-twins-samples-csharp
37 | ```
38 |
39 | The repo contains several standalone projects:
40 |
41 | * The [Occupancy](https://github.com/Azure-Samples/digital-twins-samples-csharp/tree/master/occupancy-quickstart/README.md) sample is suggested as a first example to gain familiarity with Digital Twins.
42 | * The [Device Connectivity](https://github.com/Azure-Samples/digital-twins-samples-csharp/tree/master/device-connectivity/README.md) sample demonstrates how to connect a device to Digital Twins and submit sensory data.
43 |
44 | For corresponding documentation, please see the project `README's` above.
45 |
46 | ## Visual Studio Code
47 |
48 | A [workspace file](https://github.com/Azure-Samples/digital-twins-samples-csharp/blob/master/digital-twins-samples.code-workspace) containing all the apps is included for [Visual Studio Code](https://code.visualstudio.com/) users.
49 |
50 | Alternatively, each app can be opened individually.
51 |
52 | ## Licensing and Use
53 |
54 | Azure Digital Twins Samples are [MIT Licensed](https://github.com/Azure-Samples/digital-twins-samples-csharp/blob/master/LICENSE.md).
55 |
56 | ## See also
57 |
58 | * Azure Digital Twins [product documentation](https://docs.microsoft.com/azure/digital-twins/)
59 |
60 | * Azure Digital Twins [User-defined functions client library reference](https://docs.microsoft.com/azure/digital-twins/reference-user-defined-functions-client-library)
61 |
--------------------------------------------------------------------------------
/device-connectivity/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp2.1/deviceConnectivity.dll",
14 | "args": ["GetSpaces"],
15 | "cwd": "${workspaceFolder}",
16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17 | "console": "internalConsole",
18 | "stopAtEntry": false,
19 | "internalConsoleOptions": "openOnSessionStart"
20 | },
21 | {
22 | "name": ".NET Core Attach",
23 | "type": "coreclr",
24 | "request": "attach",
25 | "processId": "${command:pickProcess}"
26 | }
27 | ,]
28 | }
--------------------------------------------------------------------------------
/device-connectivity/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/bin/": true,
4 | "**/obj/": true
5 | }
6 | }
--------------------------------------------------------------------------------
/device-connectivity/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/"
11 | ],
12 | "problemMatcher": "$msCompile"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/device-connectivity/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/device-connectivity/README.md:
--------------------------------------------------------------------------------
1 | # Digital Twins Device Connectivity Sample
2 |
3 | [](https://opensource.org/licenses/MIT) [](../CONTRIBUTING.md)
4 |
5 | This introductory sample demonstrates how to connect a device to Digital Twins and submit sample sensory data.
6 |
7 | ## Configure the app
8 |
9 | Edit your settings file to fill in the following values:
10 |
11 | * `DeviceConnectionString`
12 | * `Sensors`
13 |
14 | ### DeviceConnectionString
15 |
16 | You can get the connection string by calling Management API's Devices controller. E.g. for a device with ID `22215ed9-91e8-40af-9d1b-a22727849393`:
17 |
18 | ```plaintext
19 | GET https://{{your-instance-name}}.westcentralus.azuresmartspaces.net/management/api/v1.0/devices/22215ed9-91e8-40af-9d1b-a22727849393?includes=ConnectionString
20 | ```
21 |
22 | ```JSON
23 | {
24 | "name": "My Sample Device",
25 | "typeId": 2,
26 | "subtypeId": 1,
27 | "hardwareId": "00AABBCCDDEE",
28 | "spaceId": "ef46f69f-0b95-45ee-a41f-718b3ee4c355",
29 | "status": "Active",
30 | "id": "22215ed9-91e8-40af-9d1b-a22727849393",
31 | "connectionString": "Hostname=..."
32 | }
33 | ```
34 |
35 | Copy the `connectionString` over to the [appsettings.json](./appsettings.json) file:
36 |
37 | ```plaintext
38 | {
39 | "Settings": {
40 | "DeviceConnectionString": "Hostname=...",
41 | ...
42 | }
43 | }
44 | ```
45 |
46 | ### Sensors
47 |
48 | An array of one or more Sensors you wish to send data for using this sample. You can get the `dataType` and `hardwareId` of each Sensor by calling Management API's Sensors controller. E.g. for a sensor with ID `c4cf2f41-edd6-4fc3-a47a-6bedab6470db`:
49 |
50 | ```plaintext
51 | GET https://{{your-instance-name}}.westcentralus.azuresmartspaces.net/management/api/v1.0/sensors/c4cf2f41-edd6-4fc3-a47a-6bedab6470db?includes=Types
52 | ```
53 |
54 | ```JSON
55 | {
56 | "dataType":"Motion",
57 | "dataTypeId":27,
58 | "deviceId":"f51e5295-ca81-4517-8f5d-0b940f678ef2",
59 | "id": "c4cf2f41-edd6-4fc3-a47a-6bedab6470db",
60 | "hardwareId":"1234ABC",
61 | "name": "My Sample Sensor",
62 | "spaceId": "ef46f69f-0b95-45ee-a41f-718b3ee4c355"
63 | }
64 | ```
65 |
66 | Copy the `hardwareId` and `dataType` over to be one entry in the `Sensors` array in the [appsettings.json](./appsettings.json) file:
67 |
68 | ```JSON
69 | {
70 | "Settings": {
71 | "Sensors": [{
72 | "DataType": "Motion",
73 | "HardwareId": "1234ABC"
74 | },{
75 | "DataType": "CarbonDioxide",
76 | "HardwareId": "SOMEOTHERID"
77 | }]
78 | }
79 | }
80 | ```
81 |
82 | ## Build and Run the app
83 |
84 | 1. [Install .NET Core SDK](https://www.microsoft.com/net/core) on your execution platform.
85 | 1. Run the following commands to build the app:
86 |
87 | ```shell
88 | dotnet restore
89 | dotnet build
90 | ```
91 | 1. Run the app:
92 |
93 | ```shell
94 | dotnet run
95 | ```
96 |
97 | ## Platform-specific instructions
98 |
99 | Instructions specific to your execution platform.
100 |
101 | ### Build and run the app on a Raspberry Pi
102 |
103 | Choose `win-arm` or `linux-arm` for your platform:
104 |
105 | * Run the following:
106 |
107 | ```shell
108 | dotnet publish -r
109 | ```
110 |
111 | Under either:
112 |
113 | * `./bin/Debug/netcoreapp2.0/{{your-runtime-identifier}}/publish`
114 | * `.\bin\Debug\netcoreapp2.0\{{your-runtime-identifier}}\publish`
115 |
116 | You will see the whole self contained app that you need to copy to your Raspberry Pi.
117 |
118 | ### Windows 10 IoT
119 |
120 | * Run on device by invoking the executable.
121 |
122 | ### Linux
123 |
124 | 1. Install [Linux](https://www.raspberrypi.org/downloads/) on your Pi.
125 | 1. Install the [platform dependencies](https://github.com/dotnet/core/blob/master/Documentation/prereqs.md) for .NET Core through your distribution's package manager.
126 | 1. Run:
127 |
128 | ```bash
129 | chmod 755 ./device-connectivity
130 | ```
131 |
132 | ## Customizing the app to your needs
133 |
134 | This app compiles a sample telemetry message with some randomly generated data. You can customize the message format as well as the payload by modifying [models/CustomTelemetryMessage.cs](./models/CustomTelemetryMessage.cs). The data contract of the model can be changed to any serializable format or you can choose to compile your own payload by generating a byte array or stream that can be passed to [Message(byte[] byteArray)](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.devices.client.message.-ctor?view=azure-dotnet#Microsoft_Azure_Devices_Client_Message__ctor_System_Byte___), as found in the `SendEvent` method. You will have to maintain a set of properties to ensure your message keeps getting routed appropriately, as listed below.
135 |
136 | ### Telemetry Properties
137 |
138 | While the payload contents of a `Message` can be arbitrary data, up to 256kb in size, there are a few requirements on expected [Message.Properties](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.message.properties?view=azure-dotnet). The following is the list of required and optional properties supported by the system:
139 |
140 | | Property Name | Value | Required | Description |
141 | |---------------|-------|----------|-------------|
142 | | DigitalTwins-Telemetry | 1.0 | yes | A constant value that identifies a message to the system |
143 | | DigitalTwins-SensorHardwareId | `string(72)` | yes | A unique identifier pointing to a Sensor in the topology for which the `Message` is meant for. This value must match an object's `HardwareId` property for the system to process it. E.g.: `00FF0643BE88-PIR` |
144 | | CreationTimeUtc | `string` | no | An [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted date string identifying the sampling time of the payload. E.g.: `2018-09-20T07:35:00.8587882-07:00` |
145 | | CorrelationId | `string` | no | A `uuid` formatted as a string that can be used to trace events across the system. E.g.: `cec16751-ab27-405d-8fe6-c68e1412ce1f`|
146 |
147 | ## Licensing and Use
148 |
149 | Azure Digital Twins Samples are [MIT Licensed](./LICENSE.md).
150 |
--------------------------------------------------------------------------------
/device-connectivity/THIRD-PARTY-NOTICES.txt:
--------------------------------------------------------------------------------
1 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
2 | Do Not Translate or Localize
3 |
4 | This project incorporates components from the projects listed below. The original copyright notices
5 | and licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not
6 | expressly granted herein, whether by implication, estoppel or otherwise.
7 |
8 | 1. Newtonsoft.Json (https://github.com/JamesNK/Newtonsoft.Json)
9 | 2. YamlDotNet (https://github.com/aaubry/YamlDotNet)
10 |
11 |
12 | newtonsoft.json NOTICES AND INFORMATION BEGIN HERE
13 | =========================================
14 | The MIT License (MIT)
15 |
16 | Copyright (c) 2007 James Newton-King
17 |
18 | Permission is hereby granted, free of charge, to any person obtaining a copy of
19 | this software and associated documentation files (the "Software"), to deal in
20 | the Software without restriction, including without limitation the rights to
21 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
22 | the Software, and to permit persons to whom the Software is furnished to do so,
23 | subject to the following conditions:
24 |
25 | The above copyright notice and this permission notice shall be included in all
26 | copies or substantial portions of the Software.
27 |
28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
30 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
31 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
32 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
33 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 | =========================================
35 | END OF newtonsoft.json NOTICES AND INFORMATION
36 |
37 |
38 | YamlDotNet NOTICES AND INFORMATION BEGIN HERE
39 | =========================================
40 | Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Antoine Aubry and contributors
41 |
42 | Permission is hereby granted, free of charge, to any person obtaining a copy of
43 | this software and associated documentation files (the "Software"), to deal in
44 | the Software without restriction, including without limitation the rights to
45 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
46 | of the Software, and to permit persons to whom the Software is furnished to do
47 | so, subject to the following conditions:
48 |
49 | The above copyright notice and this permission notice shall be included in all
50 | copies or substantial portions of the Software.
51 |
52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
53 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
54 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
55 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
56 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
57 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
58 | SOFTWARE.
59 | =========================================
60 | END OF YamlDotNet NOTICES AND INFORMATION
61 |
--------------------------------------------------------------------------------
/device-connectivity/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Settings": {
3 | "DeviceConnectionString": "",
4 | "MessageIntervalInSeconds": 5,
5 | "Sensors": [{
6 | "DataType": "Motion",
7 | "HardwareId": "SAMPLE_SENSOR_MOTION"
8 | },{
9 | "DataType": "CarbonDioxide",
10 | "HardwareId": "SAMPLE_SENSOR_CARBONDIOXIDE"
11 | }]
12 | }
13 | }
--------------------------------------------------------------------------------
/device-connectivity/deviceConnectivity.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.1
6 | latest
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | PreserveNewest
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/device-connectivity/models/CustomTelemetryMessage.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Runtime.Serialization;
6 |
7 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
8 | {
9 | [DataContract(Name="CustomTelemetryMessage")]
10 | public class CustomTelemetryMessage
11 | {
12 | [DataMember(Name="SensorValue")]
13 | public string SensorValue { get; set; }
14 | }
15 | }
--------------------------------------------------------------------------------
/device-connectivity/program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Globalization;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Net.NetworkInformation;
10 | using System.Runtime.Serialization.Json;
11 | using System.Text;
12 | using System.Threading;
13 | using System.Threading.Tasks;
14 | using Microsoft.Azure.Devices.Client;
15 | using Microsoft.Azure.DigitalTwins.Samples.Models;
16 | using Microsoft.Extensions.Configuration;
17 | using Microsoft.Extensions.Configuration.Binder;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples
20 | {
21 | class Program
22 | {
23 | private static Random rnd = new Random();
24 | private static IConfigurationSection settings;
25 | static void Main(string[] args)
26 | {
27 | if (args.Length != 0)
28 | {
29 | Console.WriteLine("Usage: dotnet run\nNo arguments are supported");
30 | return;
31 | }
32 |
33 | settings = new ConfigurationBuilder()
34 | .SetBasePath(Directory.GetCurrentDirectory())
35 | .AddJsonFile("appsettings.json")
36 | .Build()
37 | .GetSection("Settings");
38 |
39 | try
40 | {
41 | DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(settings["DeviceConnectionString"]);
42 |
43 | if (deviceClient == null)
44 | {
45 | Console.WriteLine("ERROR: Failed to create DeviceClient!");
46 | return;
47 | }
48 |
49 | SendEvent(deviceClient).Wait();
50 | }
51 | catch (Exception ex)
52 | {
53 | Console.WriteLine("EXIT: Unexpected error: {0}", ex.Message);
54 | }
55 | }
56 |
57 | static Func CreateGetRandomSensorReading(string sensorDataType, int iteration)
58 | {
59 | switch (sensorDataType)
60 | {
61 | default:
62 | throw new Exception($"Unsupported configuration: SensorDataType, '{sensorDataType}'. Please check your appsettings.json.");
63 | case "Motion":
64 | if (iteration % 6 < 3)
65 | return () => "false";
66 | else
67 | return () => "true";
68 |
69 | case "Temperature":
70 | return () => rnd.Next(70, 100).ToString(CultureInfo.InvariantCulture);
71 | case "CarbonDioxide":
72 | if (iteration % 6 < 3)
73 | return () => rnd.Next(800, 1000).ToString(CultureInfo.InvariantCulture);
74 | else
75 | return () => rnd.Next(1000, 1100).ToString(CultureInfo.InvariantCulture);
76 | }
77 | }
78 |
79 | static async Task SendEvent(DeviceClient deviceClient)
80 | {
81 | var serializer = new DataContractJsonSerializer(typeof(CustomTelemetryMessage));
82 |
83 | var sensors = settings.GetSection("Sensors").Get();
84 |
85 | var delayPerMessageSend = int.Parse(settings["MessageIntervalInSeconds"]);
86 | var countOfSendsPerIteration = sensors.Length;
87 | var maxSecondsToRun = 15 * 60;
88 | var maxIterations = maxSecondsToRun / countOfSendsPerIteration / delayPerMessageSend;
89 | var curIteration = 0;
90 |
91 | do {
92 | foreach (var sensor in sensors)
93 | {
94 | var getRandomSensorReading = CreateGetRandomSensorReading(sensor.DataType, curIteration);
95 | var telemetryMessage = new CustomTelemetryMessage()
96 | {
97 | SensorValue = getRandomSensorReading(),
98 | };
99 |
100 | using (var stream = new MemoryStream())
101 | {
102 | serializer.WriteObject(stream, telemetryMessage);
103 | var byteArray = stream.ToArray();
104 | Message eventMessage = new Message(byteArray);
105 | eventMessage.Properties.Add("DigitalTwins-Telemetry", "1.0");
106 | eventMessage.Properties.Add("DigitalTwins-SensorHardwareId", $"{sensor.HardwareId}");
107 | eventMessage.Properties.Add("CreationTimeUtc", DateTime.UtcNow.ToString("o"));
108 | eventMessage.Properties.Add("x-ms-client-request-id", Guid.NewGuid().ToString());
109 |
110 | Console.WriteLine($"\t{DateTime.UtcNow.ToLocalTime()}> Sending message: {Encoding.UTF8.GetString(eventMessage.GetBytes())} Properties: {{ {eventMessage.Properties.Aggregate(new StringBuilder(), (sb, x) => sb.Append($"'{x.Key}': '{x.Value}',"), sb => sb.ToString())} }}");
111 |
112 | await deviceClient.SendEventAsync(eventMessage);
113 | }
114 | }
115 |
116 | await Task.Delay(TimeSpan.FromSeconds(delayPerMessageSend));
117 |
118 | } while (++curIteration < maxIterations);
119 |
120 | Console.WriteLine($"Finished sending {curIteration} events (per sensor type)");
121 | }
122 | }
123 |
124 | public class Sensor
125 | {
126 | public string DataType { get; set; }
127 | public string HardwareId { get; set; }
128 | }
129 | }
--------------------------------------------------------------------------------
/digital-twins-samples.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "occupancy-quickstart"
5 | },
6 | {
7 | "path": "device-connectivity"
8 | }
9 | ],
10 | "settings": {}
11 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/src/bin/Debug/netcoreapp2.1/occupancyQuickstart.dll",
14 | "args": ["GetSpaces"],
15 | "cwd": "${workspaceFolder}/src",
16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17 | "console": "internalConsole",
18 | "stopAtEntry": false,
19 | "internalConsoleOptions": "openOnSessionStart"
20 | },
21 | {
22 | "name": ".NET Core Attach",
23 | "type": "coreclr",
24 | "request": "attach",
25 | "processId": "${command:pickProcess}"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/occupancy-quickstart/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/bin/": true,
4 | "**/obj/": true
5 | }
6 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/src"
11 | ],
12 | "problemMatcher": "$msCompile"
13 | },
14 | {
15 | "label": "test",
16 | "command": "dotnet",
17 | "type": "process",
18 | "args": [
19 | "test",
20 | "${workspaceFolder}/tests"
21 | ],
22 | "problemMatcher": "$msCompile"
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/occupancy-quickstart/README.md:
--------------------------------------------------------------------------------
1 | # Occupancy Sample
2 |
3 | [](https://opensource.org/licenses/MIT) [](../CONTRIBUTING.md)
4 |
5 | Sample code to provision and read resources in a Digital Twins topology via management APIs. It also creates an example function that runs within the Digital Twins instance that computes motion from a sensor in a room in order to determine the occupancy.
6 |
7 | ## Build and Run the Sample
8 |
9 | Below are some details on how to get up and running. For a more detailed walkthrough or more details on how to get the values described below, please see this [quickstart doc](https://docs.microsoft.com/azure/digital-twins/quickstart-view-occupancy-dotnet).
10 |
11 | ### Update appSettings.json
12 |
13 | [appSettings.json](./src/appSettings.json) is used to specify info on which Digital Twins instance to connect to. The three fields you will need to fill in are:
14 |
15 | - `ClientId`: The **application ID** of a native Azure Active Directory app that has permissions to call the Azure Digital Twins service.
16 | - `Tenant`: The **directory ID** of a your Azure Active Directory.
17 | - `BaseUrl`: The management api url to your Digital Twins instance (see `appSetting.json` for what this should look like).
18 | - `AadRedirectUri`: A valid **Redirect URI** configured for your Azure Active Directory app. We recommend using the default **Redirect URI** `http://www.localhost:8080`. However, you may also choose another port or domain as required.
19 |
20 | ### Use a shell
21 |
22 | 1. Run the app:
23 |
24 | ```shell
25 | cd src
26 | dotnet restore
27 | dotnet run
28 | ```
29 | This will show usage. For a walkthrough of what you can do see [quickstart doc](https://docs.microsoft.com/azure/digital-twins/quickstart-view-occupancy-dotnet).
30 |
31 | 1. Run tests:
32 |
33 | ```shell
34 | dotnet test ../tests
35 | ```
36 |
37 | ### Use Visual Studio Code
38 |
39 | 1. Open the 'occupancy-quickstart' folder in Visual Studio Code.
40 | 1. Run the app by using the `F5` key. You can change the command-line parameters in `launch.json`.
41 | 1. To build and run tests use the 'Run Task' command in Visual Studio Code and choose `test`.
42 |
43 | ## Notes
44 |
45 | ### Authentication
46 |
47 | To learn more about configuration, security, and API authentication:
48 |
49 | 1. See the [role-based access control doc](https://docs.microsoft.com/azure/digital-twins/security-role-based-access-control).
50 | 1. See the [Management API doc](https://docs.microsoft.com/azure/digital-twins/security-authenticating-apis).
51 |
52 | ## Problems
53 |
54 | If you run into problems or have questions checkout our [FAQ](./docs/faq.md). If that doesn't help search open and closed [issues](https://github.com/Azure-Samples/digital-twins-samples-csharp/issues) and open a new one if you can't find an answer.
55 |
56 | ## Licensing and Use
57 |
58 | Azure Digital Twins Samples are [MIT Licensed](./LICENSE.md).
59 |
--------------------------------------------------------------------------------
/occupancy-quickstart/docs/faq.md:
--------------------------------------------------------------------------------
1 | ## Occupany Sample FAQ
2 |
3 | #### Exception: YamlDotNet.Core.SyntaxErrorException
4 |
5 | ```
6 | Exception: YamlDotNet.Core.SyntaxErrorException: (Line: X, Col: Y, Idx: ZZ) - (Line: X, Col: Y, Idx: ZZ): While scanning for the next token, find character that cannot start any token.
7 | ```
8 |
9 | Yaml files don't support tab characters. NOTE: that visual studio 2017 doesn't know (without customizations) to insert spaces instead of tabs into yaml files. More details [here](https://developercommunity.visualstudio.com/content/problem/71238/editor-inserts-tabs-instead-of-spaces-for-yaml-fil.html)
10 |
11 |
12 | #### Response Status: 404, NotFound
13 |
14 | ```
15 | trce: DigitalTwinsQuickstart[0]
16 | Request: GET https://yourResource.yourLocation.azuresmartspaces.net/management/somethingWrong
17 | trce: DigitalTwinsQuickstart[0]
18 | Response Status: 404, NotFound,
19 | ```
20 |
21 | A 404 usually indicates the `BaseUrl` in appSettings.json (or appSettings.dev.json) is misconfigured.
22 |
23 |
24 | #### Response Status: 401, Unauthorized
25 |
26 | There are several conditions that can cause a 401. Below are different errors that can be included as part of a 401 and possible corrections.
27 |
28 | ##### The request body must contain the following parameter: 'client_secret or client_assertion
29 | ```
30 | trce: DigitalTwinsQuickstart[0]
31 | Response Status: 401, Unauthorized, {"message":"Authorization has been denied for this request."}
32 | To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code ZZZZZZZ to authenticate.
33 | Exception: Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException: AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'.
34 | ```
35 |
36 | This usually indicates the aad app you are using (as represented by `ClientId` in appSettings.json or appSettings.dev.json)
37 | is an aad *web* app instead of an aad *native* app).
38 |
39 |
40 | #### Response Status: 403, Forbidden
41 |
42 | This usually is because the user account used for login (when prompted by this app) is not authenticated in the Digital Twins' Management Api. See [role-based access control](https://docs.microsoft.com/en-us/azure/digital-twins/security-role-based-access-control) for more info.
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/actionNames.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples
5 | {
6 | enum ActionName
7 | {
8 | CreateEndpoints,
9 | CreateRoleAssignments,
10 | GetAvailableAndFreshSpaces,
11 | GetOntologies,
12 | GetSpaces,
13 | ProvisionSample,
14 | }
15 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/createEndpoints.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Net.Http;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using Microsoft.Extensions.Logging;
12 | using Newtonsoft.Json;
13 | using YamlDotNet.Serialization;
14 |
15 | namespace Microsoft.Azure.DigitalTwins.Samples
16 | {
17 | public static partial class Actions
18 | {
19 | public static async Task> CreateEndpoints(HttpClient httpClient, ILogger logger)
20 | {
21 | IEnumerable endpointDescriptions;
22 | using (var r = new StreamReader("actions/createEndpoints.yaml"))
23 | {
24 | endpointDescriptions = await GetCreateEndpointsDescriptions(r);
25 | }
26 |
27 | var createdIds = (await CreateEndpoints(httpClient, logger, endpointDescriptions)).ToList();
28 |
29 | Console.WriteLine($"CreateEndpoints completed. {GetCreationSummary("endpoint", "endpoints", createdIds)}");
30 |
31 | return createdIds;
32 | }
33 |
34 | public static async Task> GetCreateEndpointsDescriptions(TextReader textReader)
35 | => new Deserializer().Deserialize>(await textReader.ReadToEndAsync());
36 |
37 | public static async Task> CreateEndpoints(
38 | HttpClient httpClient,
39 | ILogger logger,
40 | IEnumerable descriptions)
41 | {
42 | var endpointIds = new List();
43 | foreach (var description in descriptions)
44 | {
45 | endpointIds.Add(await Api.CreateEndpoints(httpClient, logger, description.ToEndpointCreate()));
46 | }
47 | return endpointIds.Where(id => id != Guid.Empty);
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/createEndpoints.yaml:
--------------------------------------------------------------------------------
1 | - type: EventGrid
2 | eventTypes:
3 | - SensorChange
4 | - SpaceChange
5 | - TopologyOperation
6 | - UdfCustom
7 | connectionString:
8 | secondaryConnectionString:
9 | path:
10 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/createRoleAssignments.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Net.Http;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using Microsoft.Extensions.Logging;
12 | using Newtonsoft.Json;
13 | using YamlDotNet.Serialization;
14 |
15 | namespace Microsoft.Azure.DigitalTwins.Samples
16 | {
17 | public static partial class Actions
18 | {
19 | public static async Task> CreateRoleAssignments(HttpClient httpClient, ILogger logger)
20 | {
21 | IEnumerable roleAssignmentsDescriptions;
22 | using (var r = new StreamReader("actions/createRoleAssignments.yaml"))
23 | {
24 | roleAssignmentsDescriptions = await GetCreateRoleAssignmentsDescriptions(r);
25 | }
26 |
27 | var createdIds = (await CreateRoleAssignments(httpClient, logger, roleAssignmentsDescriptions)).ToList();
28 |
29 | Console.WriteLine($"CreateRoleAssignments completed: {GetCreationSummary("role assignment", "role assignments", createdIds)}");
30 |
31 | return createdIds;
32 | }
33 |
34 | public static async Task> GetCreateRoleAssignmentsDescriptions(TextReader textReader)
35 | => new Deserializer().Deserialize>(await textReader.ReadToEndAsync());
36 |
37 | public static async Task> CreateRoleAssignments(
38 | HttpClient httpClient,
39 | ILogger logger,
40 | IEnumerable descriptions)
41 | {
42 | var roleAssignmentIds = new List();
43 | foreach (var description in descriptions)
44 | {
45 | roleAssignmentIds.Add(await Api.CreateRoleAssignment(httpClient, logger, description.ToRoleAssignmentCreate()));
46 | }
47 |
48 | return roleAssignmentIds.Where(id => id != Guid.Empty);
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/createRoleAssignments.yaml:
--------------------------------------------------------------------------------
1 | # Examples of how to add a user as a space admin to the root.
2 | # See docs for more details or how to add other types to Digital Twins role-based access controls
3 | # To use: Substitute and with appropriate values from your AAD directory and run CreateRoleAssignments
4 | - objectId: # ObjectId (guid) of user to add
5 | objectIdType: UserId
6 | path: /
7 | roleId: 98e44ad7-28d4-4007-853b-b9968ad132d1 # System Role: SpaceAdministrator
8 | tenantId: # aka the AAD DirectoryId
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/descriptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | // Descriptions are a representation of a space topology that are specific to this app
8 | // They are an in memory version of the yaml file in this directory
9 |
10 | namespace Microsoft.Azure.DigitalTwins.Samples
11 | {
12 | public class DeviceDescription
13 | {
14 | public string hardwareId { get; set; }
15 | public string name { get; set; }
16 |
17 | [JsonIgnore]
18 | public IEnumerable sensors { get; set; }
19 | }
20 |
21 | public class EndpointDescription
22 | {
23 | public string type { get; set; }
24 | public string[] eventTypes { get; set; }
25 | public string connectionString { get; set; }
26 | public string secondaryConnectionString { get; set; }
27 | public string path { get; set; }
28 | }
29 |
30 | public class KeyStoreDescription
31 | {
32 | public string name { get; set; }
33 | }
34 |
35 | public class MatcherDescription
36 | {
37 | public string name { get; set; }
38 | public string dataTypeValue { get; set; }
39 | }
40 |
41 | public class ResourceDescription
42 | {
43 | public string type { get; set; }
44 | }
45 |
46 | public class RoleAssignmentDescription
47 | {
48 | public string objectId { get; set; }
49 | public string objectIdType { get; set; }
50 | public string objectName { get; set; }
51 | public string path { get; set; }
52 | public string roleId { get; set; }
53 | public string tenantId { get; set; }
54 | }
55 |
56 | public class SensorDescription
57 | {
58 | public string dataType { get; set; }
59 | public string hardwareId { get; set; }
60 | }
61 |
62 | public class UserDefinedFunctionDescription
63 | {
64 | public string name { get; set; }
65 | public IEnumerable matcherNames { get; set; }
66 | public string script { get; set; }
67 | }
68 |
69 | public class SpaceDescription
70 | {
71 | public string name { get; set; }
72 | public string type { get; set; }
73 | public string subType { get; set; }
74 |
75 | [JsonIgnore]
76 | public IEnumerable devices { get; set; }
77 |
78 | [JsonIgnore]
79 | public IEnumerable keystores { get; set; }
80 |
81 | [JsonIgnore]
82 | public IEnumerable matchers { get; set; }
83 |
84 | [JsonIgnore]
85 | public IEnumerable roleassignments { get; set; }
86 |
87 | [JsonIgnore]
88 | public IEnumerable resources { get; set; }
89 |
90 | [JsonIgnore]
91 | public IEnumerable spaces { get; set; }
92 |
93 | [JsonIgnore]
94 | public IEnumerable userdefinedfunctions { get; set; }
95 | }
96 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/descriptionsExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 |
8 | // These extensions translate the descriptions (which are in memory representations
9 | // of the sample yaml file) to Digital Twins models.
10 |
11 | namespace Microsoft.Azure.DigitalTwins.Samples
12 | {
13 | public static class DescriptionExtensions
14 | {
15 | public static Models.DeviceCreate ToDeviceCreate(this DeviceDescription description, Guid spaceId)
16 | => new Models.DeviceCreate()
17 | {
18 | HardwareId = description.hardwareId,
19 | Name = description.name,
20 | SpaceId = spaceId.ToString(),
21 | };
22 |
23 | public static Models.EndpointsCreate ToEndpointCreate(this EndpointDescription description)
24 | => new Models.EndpointsCreate()
25 | {
26 | ConnectionString = description.connectionString,
27 | EventTypes = description.eventTypes,
28 | Path = description.path,
29 | SecondaryConnectionString = description.secondaryConnectionString,
30 | Type = description.type,
31 | };
32 |
33 | public static Models.MatcherCreate ToMatcherCreate(this MatcherDescription description, Guid spaceId)
34 | => new Models.MatcherCreate()
35 | {
36 | Name = description.name,
37 | SpaceId = spaceId.ToString(),
38 | Conditions = new [] {
39 | new Models.ConditionCreate()
40 | {
41 | Target = "Sensor",
42 | Path = "$.dataType",
43 | Value = $"\"{description.dataTypeValue}\"",
44 | Comparison = "Equals",
45 | }
46 | }
47 | };
48 |
49 | public static Models.KeyStoreCreate ToKeyStoreCreate(this KeyStoreDescription description, Guid spaceId)
50 | => new Models.KeyStoreCreate()
51 | {
52 | Name = description.name,
53 | SpaceId = spaceId.ToString(),
54 | };
55 |
56 | public static Models.RoleAssignmentCreate ToRoleAssignmentCreate(this RoleAssignmentDescription description, string objectId = null, string path = null)
57 | => new Models.RoleAssignmentCreate()
58 | {
59 | ObjectId = objectId ?? description.objectId,
60 | ObjectIdType = description.objectIdType,
61 | Path = path ?? description.path,
62 | RoleId = description.roleId,
63 | TenantId = description.tenantId,
64 | };
65 |
66 | public static Models.ResourceCreate ToResourceCreate(this ResourceDescription description, Guid spaceId)
67 | => new Models.ResourceCreate()
68 | {
69 | SpaceId = spaceId.ToString(),
70 | Type = description.type,
71 | };
72 |
73 | public static Models.SensorCreate ToSensorCreate(this SensorDescription description, Guid deviceId)
74 | => new Models.SensorCreate()
75 | {
76 | DataType = description.dataType,
77 | DeviceId = deviceId.ToString(),
78 | HardwareId = description.hardwareId,
79 | };
80 |
81 | public static Models.SpaceCreate ToSpaceCreate(this SpaceDescription description, Guid parentId)
82 | => new Models.SpaceCreate()
83 | {
84 | Name = description.name,
85 | ParentSpaceId = parentId != Guid.Empty ? parentId.ToString() : "",
86 | };
87 |
88 | public static Models.UserDefinedFunction ToUserDefinedFunction(this UserDefinedFunctionDescription description, string Id, Guid spaceId, IEnumerable matchers)
89 | => new Models.UserDefinedFunction()
90 | {
91 | Id = Id,
92 | Name = description.name,
93 | SpaceId = spaceId.ToString(),
94 | Matchers = matchers,
95 | };
96 |
97 | public static Models.UserDefinedFunctionCreate ToUserDefinedFunctionCreate(this UserDefinedFunctionDescription description, Guid spaceId, IEnumerable matcherIds)
98 | => new Models.UserDefinedFunctionCreate()
99 | {
100 | Name = description.name,
101 | SpaceId = spaceId.ToString(),
102 | Matchers = matcherIds,
103 | };
104 |
105 | public static Models.UserDefinedFunctionUpdate ToUserDefinedFunctionUpdate(this UserDefinedFunctionDescription description, string id, Guid spaceId, IEnumerable matcherIds)
106 | => new Models.UserDefinedFunctionUpdate()
107 | {
108 | Id = id,
109 | Name = description.name,
110 | SpaceId = spaceId.ToString(),
111 | Matchers = matcherIds,
112 | };
113 | }
114 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/getAvailableAndFreshSpaces.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Logging;
10 | using Newtonsoft.Json;
11 |
12 | namespace Microsoft.Azure.DigitalTwins.Samples
13 | {
14 | public static partial class Actions
15 | {
16 | // Prints out and returns spaces with the occupany property key set
17 | public static async Task GetAvailableAndFreshSpaces(HttpClient httpClient)
18 | {
19 | Console.WriteLine("Polling spaces with 'AvailableAndFresh' value type");
20 |
21 | var maxGets = 30;
22 | for (var curGets = 0; curGets < maxGets; ++curGets)
23 | {
24 | var (spaces, response) = await GetManagementItemsAsync(httpClient, "spaces", "includes=values");
25 | if (spaces == null)
26 | {
27 | var content = await response.Content?.ReadAsStringAsync();
28 | Console.WriteLine($"ERROR: GET spaces?includes=values failed with: {(int)response.StatusCode}, {response.StatusCode} {content}");
29 | break;
30 | }
31 |
32 | var availableAndFreshSpaces = spaces.Where(s => s.Values != null && s.Values.Any(v => v.Type == "AvailableAndFresh"));
33 | if (availableAndFreshSpaces.Any())
34 | {
35 | var availableAndFreshDisplay = availableAndFreshSpaces
36 | .Select(s => GetDisplayValues(s))
37 | .Aggregate((acc, cur) => acc + "\n" + cur);
38 | Console.WriteLine($"{availableAndFreshDisplay}");
39 | }
40 | else
41 | {
42 | Console.WriteLine("Unable to find a space with value type 'AvailableAndFresh'");
43 | }
44 |
45 | await Task.Delay(TimeSpan.FromSeconds(4));
46 | }
47 | }
48 |
49 | private static async Task<(IEnumerable, HttpResponseMessage)> GetManagementItemsAsync(
50 | HttpClient httpClient,
51 | string queryItem,
52 | string queryParams)
53 | {
54 | var response = await httpClient.GetAsync($"{queryItem}?{queryParams}");
55 | if (response.IsSuccessStatusCode)
56 | {
57 | var content = await response.Content.ReadAsStringAsync();
58 | var objects = JsonConvert.DeserializeObject>(content);
59 | return (objects, response);
60 | }
61 |
62 | return (null, response);
63 | }
64 |
65 | private static string GetDisplayValues(Models.Space space)
66 | {
67 | var spaceValue = space.Values.First(v => v.Type == "AvailableAndFresh");
68 | return $"Name: {space.Name}\nId: {space.Id}\nTimestamp: {spaceValue.Timestamp}\nValue: {spaceValue.Value}\n";
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/getCreationSummary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Microsoft.Azure.DigitalTwins.Samples
6 | {
7 | public static partial class Actions
8 | {
9 | private static string GetCreationSummary(string itemTypeSingular, string itemTypePlural, List createdIds)
10 | => createdIds.Count == 0
11 | ? $"Created 0 {itemTypePlural}."
12 | : createdIds.Count == 1
13 | ? $"Created 1 {itemTypeSingular}: {AggregateIdsIntoString(createdIds)}"
14 | : $"Created {createdIds.Count} {itemTypePlural}: {AggregateIdsIntoString(createdIds)}";
15 |
16 | private static string AggregateIdsIntoString(IEnumerable ids)
17 | => ids
18 | .Select(id => id.ToString())
19 | .Aggregate((acc, cur) => acc + ", " + cur);
20 | }
21 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/getSpaces.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Logging;
10 | using Newtonsoft.Json;
11 |
12 | namespace Microsoft.Azure.DigitalTwins.Samples
13 | {
14 | public static partial class Actions
15 | {
16 | public static async Task GetSpaces(HttpClient httpClient, ILogger logger)
17 | {
18 | var spaces = await Api.GetSpaces(
19 | httpClient, logger,
20 | maxNumberToGet: 100, includes: "types,values,properties");
21 |
22 | Console.WriteLine($"GetSpaces: {JsonConvert.SerializeObject(spaces, Formatting.Indented)}");
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/provisionResults.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 |
9 | namespace Microsoft.Azure.DigitalTwins.Samples.ProvisionResults
10 | {
11 | public struct Space
12 | {
13 | public Guid Id;
14 | public IEnumerable Devices;
15 | public IEnumerable Sensors;
16 | public IEnumerable Spaces;
17 | }
18 |
19 | public struct Device
20 | {
21 | public string ConnectionString;
22 | public string HardwareId;
23 | }
24 |
25 | public struct Sensor
26 | {
27 | public string DataType;
28 | public string HardwareId;
29 | }
30 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/provisionSample.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Net.Http;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using Microsoft.Extensions.Logging;
12 | using Newtonsoft.Json;
13 | using YamlDotNet.Serialization;
14 |
15 | namespace Microsoft.Azure.DigitalTwins.Samples
16 | {
17 | public static partial class Actions
18 | {
19 | public static async Task> ProvisionSample(HttpClient httpClient, ILogger logger)
20 | {
21 | IEnumerable spaceCreateDescriptions;
22 | using (var r = new StreamReader("actions/provisionSample.yaml"))
23 | {
24 | spaceCreateDescriptions = await GetProvisionSampleTopology(r);
25 | }
26 |
27 | var results = await CreateSpaces(httpClient, logger, spaceCreateDescriptions, Guid.Empty);
28 |
29 | Console.WriteLine($"Completed Provisioning: {JsonConvert.SerializeObject(results, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } )}");
30 |
31 | return results;
32 | }
33 |
34 | public static async Task> GetProvisionSampleTopology(TextReader textReader)
35 | => new Deserializer().Deserialize>(await textReader.ReadToEndAsync());
36 |
37 | public static async Task> CreateSpaces(
38 | HttpClient httpClient,
39 | ILogger logger,
40 | IEnumerable descriptions,
41 | Guid parentId)
42 | {
43 | var spaceResults = new List();
44 | foreach (var description in descriptions)
45 | {
46 | var spaceId = await GetExistingSpaceOrCreate(httpClient, logger, parentId, description);
47 |
48 | if (spaceId != Guid.Empty)
49 | {
50 | // This must happen before devices (or anyhting that could have devices like other spaces)
51 | // or the device create will fail because a resource is required on an ancestor space
52 | if (description.resources != null)
53 | await CreateResources(httpClient, logger, description.resources, spaceId);
54 |
55 | var devices = description.devices != null
56 | ? await CreateDevices(httpClient, logger, description.devices, spaceId)
57 | : Array.Empty();
58 |
59 | if (description.matchers != null)
60 | await CreateMatchers(httpClient, logger, description.matchers, spaceId);
61 |
62 | if (description.userdefinedfunctions != null)
63 | await CreateUserDefinedFunctions(httpClient, logger, description.userdefinedfunctions, spaceId);
64 |
65 | if (description.roleassignments != null)
66 | await CreateRoleAssignments(httpClient, logger, description.roleassignments, spaceId);
67 |
68 | var childSpacesResults = description.spaces != null
69 | ? await CreateSpaces(httpClient, logger, description.spaces, spaceId)
70 | : Array.Empty();
71 |
72 | var sensors = await Api.GetSensorsOfSpace(httpClient, logger, spaceId);
73 |
74 | spaceResults.Add(new ProvisionResults.Space()
75 | {
76 | Id = spaceId,
77 | Devices = devices.Select(device => new ProvisionResults.Device()
78 | {
79 | ConnectionString = device.ConnectionString,
80 | HardwareId = device.HardwareId,
81 | }),
82 | Sensors = sensors.Select(sensor => new ProvisionResults.Sensor()
83 | {
84 | DataType = sensor.DataType,
85 | HardwareId = sensor.HardwareId,
86 | }),
87 | Spaces = childSpacesResults,
88 | });
89 | }
90 | }
91 |
92 | return spaceResults;
93 | }
94 |
95 | private static async Task> CreateDevices(
96 | HttpClient httpClient,
97 | ILogger logger,
98 | IEnumerable descriptions,
99 | Guid spaceId)
100 | {
101 | if (spaceId == Guid.Empty)
102 | throw new ArgumentException("Devices must have a spaceId");
103 |
104 | var devices = new List();
105 |
106 | foreach (var description in descriptions)
107 | {
108 | var device = await GetExistingDeviceOrCreate(httpClient, logger, spaceId, description);
109 |
110 | if (device != null)
111 | {
112 | devices.Add(device);
113 |
114 | if (description.sensors != null)
115 | await CreateSensors(httpClient, logger, description.sensors, Guid.Parse(device.Id));
116 | }
117 | }
118 |
119 | return devices;
120 | }
121 |
122 | private static async Task CreateMatchers(
123 | HttpClient httpClient,
124 | ILogger logger,
125 | IEnumerable descriptions,
126 | Guid spaceId)
127 | {
128 | if (spaceId == Guid.Empty)
129 | throw new ArgumentException("Matchers must have a spaceId");
130 |
131 | foreach (var description in descriptions)
132 | {
133 | await Api.CreateMatcher(httpClient, logger, description.ToMatcherCreate(spaceId));
134 | }
135 | }
136 |
137 | private static async Task CreateResources(
138 | HttpClient httpClient,
139 | ILogger logger,
140 | IEnumerable descriptions,
141 | Guid spaceId)
142 | {
143 | if (spaceId == Guid.Empty)
144 | throw new ArgumentException("Resources must have a spaceId");
145 |
146 | foreach (var description in descriptions)
147 | {
148 | var createdId = await Api.CreateResource(httpClient, logger, description.ToResourceCreate(spaceId));
149 | if (createdId != Guid.Empty)
150 | {
151 | // After creation resources might take time to be ready to use so we need
152 | // to poll until it is done since downstream operations (like device creation)
153 | // may depend on it
154 | logger.LogInformation("Polling until resource is no longer in 'Provisioning' state...");
155 | while (await Api.IsResourceProvisioning(httpClient, logger, createdId))
156 | {
157 | await Task.Delay(5000);
158 | }
159 | }
160 | }
161 | }
162 |
163 | private static async Task CreateRoleAssignments(
164 | HttpClient httpClient,
165 | ILogger logger,
166 | IEnumerable descriptions,
167 | Guid spaceId)
168 | {
169 | if (spaceId == Guid.Empty)
170 | throw new ArgumentException("RoleAssignments must have a spaceId");
171 |
172 | var space = await Api.GetSpace(httpClient, logger, spaceId, includes: "fullpath");
173 |
174 | // A SpacePath is the list of spaces formatted like so: "space1/space2" - where space2 has space1 as a parent
175 | // When getting SpacePaths of a space itself there is always exactly one path - the path from the root to itself
176 | // This is not true when getting space paths of other topology items (ie non spaces)
177 | var path = space.SpacePaths.Single();
178 |
179 | foreach (var description in descriptions)
180 | {
181 | string objectId;
182 | switch (description.objectIdType)
183 | {
184 | case "UserDefinedFunctionId":
185 | objectId = (await Api.FindUserDefinedFunction(httpClient, logger, description.objectName, spaceId))?.Id;
186 | break;
187 | default:
188 | objectId = null;
189 | logger.LogError($"roleAssignment with objectName must have known objectIdType but instead has '{description.objectIdType}'");
190 | break;
191 | }
192 |
193 | if (objectId != null)
194 | {
195 | await Api.CreateRoleAssignment(httpClient, logger, description.ToRoleAssignmentCreate(objectId, path));
196 | }
197 | }
198 | }
199 |
200 | private static async Task CreateSensors(HttpClient httpClient, ILogger logger, IEnumerable descriptions, Guid deviceId)
201 | {
202 | if (deviceId == Guid.Empty)
203 | throw new ArgumentException("Sensors must have a deviceId");
204 |
205 | foreach (var description in descriptions)
206 | {
207 | await Api.CreateSensor(httpClient, logger, description.ToSensorCreate(deviceId));
208 | }
209 | }
210 |
211 | private static async Task CreateUserDefinedFunctions(
212 | HttpClient httpClient,
213 | ILogger logger,
214 | IEnumerable descriptions,
215 | Guid spaceId)
216 | {
217 | if (spaceId == Guid.Empty)
218 | throw new ArgumentException("UserDefinedFunctions must have a spaceId");
219 |
220 | foreach (var description in descriptions)
221 | {
222 | var matchers = await Api.FindMatchers(httpClient, logger, description.matcherNames, spaceId);
223 |
224 | using (var r = new StreamReader(description.script))
225 | {
226 | var js = await r.ReadToEndAsync();
227 | if (String.IsNullOrWhiteSpace(js))
228 | {
229 | logger.LogError($"Error creating user defined function: Couldn't read from {description.script}");
230 | }
231 | else
232 | {
233 | await CreateOrPatchUserDefinedFunction(
234 | httpClient,
235 | logger,
236 | description,
237 | js,
238 | spaceId,
239 | matchers);
240 | }
241 | }
242 | }
243 | }
244 |
245 | private static async Task GetExistingDeviceOrCreate(HttpClient httpClient, ILogger logger, Guid spaceId, DeviceDescription description)
246 | {
247 | // NOTE: The API doesn't support getting connection strings on bulk get devices calls so we
248 | // even in the case where we are reusing a preexisting device we need to make the GetDevice
249 | // call below to get the connection string
250 | var existingDeviceId = (await Api.FindDevice(httpClient, logger, description.hardwareId, spaceId))?.Id;
251 | var deviceId = existingDeviceId != null
252 | ? Guid.Parse(existingDeviceId)
253 | : await Api.CreateDevice(httpClient, logger, description.ToDeviceCreate(spaceId));
254 | return await Api.GetDevice(httpClient, logger, deviceId, includes: "ConnectionString");
255 | }
256 |
257 | private static async Task GetExistingSpaceOrCreate(HttpClient httpClient, ILogger logger, Guid parentId, SpaceDescription description)
258 | {
259 | var existingSpace = await Api.FindSpace(httpClient, logger, description.name, parentId);
260 | return existingSpace?.Id != null
261 | ? Guid.Parse(existingSpace.Id)
262 | : await Api.CreateSpace(httpClient, logger, description.ToSpaceCreate(parentId));
263 | }
264 |
265 | private static async Task CreateOrPatchUserDefinedFunction(
266 | HttpClient httpClient,
267 | ILogger logger,
268 | UserDefinedFunctionDescription description,
269 | string js,
270 | Guid spaceId,
271 | IEnumerable matchers)
272 | {
273 | var userDefinedFunction = await Api.FindUserDefinedFunction(httpClient, logger, description.name, spaceId);
274 |
275 | if (userDefinedFunction != null)
276 | {
277 | await Api.UpdateUserDefinedFunction(
278 | httpClient,
279 | logger,
280 | description.ToUserDefinedFunctionUpdate(userDefinedFunction.Id, spaceId, matchers.Select(m => m.Id)),
281 | js);
282 | }
283 | else
284 | {
285 | await Api.CreateUserDefinedFunction(
286 | httpClient,
287 | logger,
288 | description.ToUserDefinedFunctionCreate(spaceId, matchers.Select(m => m.Id)),
289 | js);
290 | }
291 | }
292 | }
293 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/provisionSample.yaml:
--------------------------------------------------------------------------------
1 | - name: Quickstart Building
2 | type: Venue
3 | resources:
4 | - type: IoTHub
5 | spaces:
6 | - name: Floor 1
7 | type: Floor
8 | spaces:
9 | - name: Area A
10 | type: Area
11 | - name: Conference Room 11
12 | type: Room
13 | subType: ConferenceRoom
14 | - name: Focus Room A1
15 | type: Room
16 | subType: FocusRoom
17 | devices:
18 | - name: Raspberry Pi 3 A1
19 | hardwareId: 1234567890AB
20 | sensors:
21 | - dataType: Motion
22 | hardwareId: SAMPLE_SENSOR_MOTION
23 | - dataType: CarbonDioxide
24 | hardwareId: SAMPLE_SENSOR_CARBONDIOXIDE
25 | # - dataType: Temperature
26 | # hardwareId: SAMPLE_SENSOR_TEMPERATURE
27 | matchers:
28 | - name: Matcher Motion A1
29 | dataTypeValue: Motion
30 | - name: Matcher CarbonDioxide A1
31 | dataTypeValue: CarbonDioxide
32 | # - name: Matcher Temperature
33 | # dataTypeValue: Temperature
34 | userdefinedfunctions:
35 | - name: Motion Processor
36 | matcherNames:
37 | - Matcher Motion A1
38 | - Matcher CarbonDioxide A1
39 | # - Matcher Temperature
40 | script: actions/userDefinedFunctions/availability.js
41 | roleassignments:
42 | - roleId: 98e44ad7-28d4-4007-853b-b9968ad132d1 # System Role: SpaceAdministrator
43 | objectName: Motion Processor
44 | objectIdType: UserDefinedFunctionId
45 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/userDefinedFunctions/availability.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | var carbonDioxideType = "CarbonDioxide";
5 | var motionType = "Motion";
6 | var spaceAvailFresh = "AvailableAndFresh";
7 | var carbonDioxideThreshold = 1000.0;
8 | // Add your sensor type here
9 |
10 | function process(telemetry, executionContext) {
11 |
12 | try {
13 | // Log SensorId and Message
14 | log(`Sensor ID: ${telemetry.SensorId}. `);
15 | log(`Sensor value: ${JSON.stringify(telemetry.Message)}.`);
16 |
17 | // Get sensor metadata
18 | var sensor = getSensorMetadata(telemetry.SensorId);
19 |
20 | // Retrieve the sensor reading
21 | var parseReading = JSON.parse(telemetry.Message);
22 |
23 | // Set the sensor reading as the current value for the sensor.
24 | setSensorValue(telemetry.SensorId, sensor.DataType, parseReading.SensorValue);
25 |
26 | // Get parent space
27 | var parentSpace = sensor.Space();
28 |
29 | // Get children sensors from the same space
30 | var otherSensors = parentSpace.ChildSensors();
31 |
32 | // Retrieve carbonDioxide, and motion sensors
33 | var carbonDioxideSensor = otherSensors.find(function(element) {
34 | return element.DataType === carbonDioxideType;
35 | });
36 |
37 | var motionSensor = otherSensors.find(function(element) {
38 | return element.DataType === motionType;
39 | });
40 |
41 | // Add your sensor variable here
42 |
43 | // get latest values for above sensors
44 | var motionValue = motionSensor.Value().Value;
45 | var presence = !!motionValue && motionValue.toLowerCase() === "true";
46 | var carbonDioxideValue = getFloatValue(carbonDioxideSensor.Value().Value);
47 |
48 | // Add your sensor latest value here
49 |
50 | // Return if no motion or carbonDioxide found return
51 | // Modify this line to monitor your sensor value
52 | if(carbonDioxideValue === null || motionValue === null) {
53 | sendNotification(telemetry.SensorId, "Sensor", "Error: Carbon dioxide or motion are null, returning");
54 | return;
55 | }
56 |
57 | // Modify these lines as per your sensor
58 | var availableFresh = "Room is available and air is fresh";
59 | var noAvailableOrFresh = "Room is not available or air quality is poor";
60 |
61 | // Modify this code block for your sensor
62 | // If carbonDioxide less than threshold and no presence in the room => log, notify and set parent space computed value
63 | if(carbonDioxideValue < carbonDioxideThreshold && !presence) {
64 | log(`${availableFresh}. Carbon Dioxide: ${carbonDioxideValue}. Presence: ${presence}.`);
65 | setSpaceValue(parentSpace.Id, spaceAvailFresh, availableFresh);
66 | }
67 | else {
68 | log(`${noAvailableOrFresh}. Carbon Dioxide: ${carbonDioxideValue}. Presence: ${presence}.`);
69 | setSpaceValue(parentSpace.Id, spaceAvailFresh, noAvailableOrFresh);
70 |
71 | // Set up custom notification for poor air quality
72 | parentSpace.Notify(JSON.stringify(noAvailableOrFresh));
73 | }
74 | }
75 | catch (error)
76 | {
77 | log(`An error has occurred processing the UDF Error: ${error.name} Message ${error.message}.`);
78 | }
79 | }
80 |
81 | function getFloatValue(str) {
82 | if(!str) {
83 | return null;
84 | }
85 |
86 | return parseFloat(str);
87 | }
88 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/userDefinedFunctions/availabilityForTutorial.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | // Sample code for the "Monitor a building with Digital Twins" tutorials:
5 |
6 | var carbonDioxideType = "CarbonDioxide";
7 | var motionType = "Motion";
8 | var spaceAvailFresh = "AvailableAndFresh";
9 | var carbonDioxideThreshold = 1000.0;
10 | var temperatureType = "Temperature";
11 | var temperatureThreshold = 78;
12 |
13 | function process(telemetry, executionContext) {
14 | try {
15 | // Log SensorId and Message
16 | log(`Sensor ID: ${telemetry.SensorId}. `);
17 | log(`Sensor value: ${JSON.stringify(telemetry.Message)}.`);
18 |
19 | // Get sensor metadata
20 | var sensor = getSensorMetadata(telemetry.SensorId);
21 |
22 | // Retrieve the sensor reading
23 | var parseReading = JSON.parse(telemetry.Message);
24 |
25 | // Set the sensor reading as the current value for the sensor.
26 | setSensorValue(telemetry.SensorId, sensor.DataType, parseReading.SensorValue);
27 |
28 | // Get parent space
29 | var parentSpace = sensor.Space();
30 |
31 | // Get children sensors from the same space
32 | var otherSensors = parentSpace.ChildSensors();
33 |
34 | // Retrieve carbonDioxide, temperature, and motion sensors
35 | var carbonDioxideSensor = otherSensors.find(function(element) {
36 | return element.DataType === carbonDioxideType;
37 | });
38 | var motionSensor = otherSensors.find(function(element) {
39 | return element.DataType === motionType;
40 | });
41 | var temperatureSensor = otherSensors.find(function(element) {
42 | return element.DataType === temperatureType;
43 | });
44 |
45 | // get latest values for above sensors
46 | var motionValue = motionSensor.Value().Value;
47 | var presence = !!motionValue && motionValue.toLowerCase() === "true";
48 | var carbonDioxideValue = getFloatValue(carbonDioxideSensor.Value().Value);
49 | var temperatureValue = getFloatValue(temperatureSensor.Value().Value);
50 |
51 | // Return if no motion, temperature, or carbonDioxide found
52 | if(carbonDioxideValue === null || motionValue === null || temperatureValue === null){
53 | sendNotification(telemetry.SensorId, "Sensor", "Error: Carbon dioxide, motion, or temperature are null, returning");
54 | return;
55 | }
56 |
57 | var alert = "Room with fresh air and comfortable temperature is available.";
58 | var noAlert = "Either room is occupied, or working conditions are not right.";
59 |
60 | // If sensor values are within range and room is available
61 | if(carbonDioxideValue < carbonDioxideThreshold && temperatureValue < temperatureThreshold && !presence) {
62 | log(`${alert}. Carbon Dioxide: ${carbonDioxideValue}. Temperature: ${temperatureValue}. Presence: ${presence}.`);
63 |
64 | // log, notify and set parent space computed value
65 | setSpaceValue(parentSpace.Id, spaceAvailFresh, alert);
66 |
67 | // Set up notification for this alert
68 | parentSpace.Notify(JSON.stringify(alert));
69 | }
70 | else {
71 | log(`${noAlert}. Carbon Dioxide: ${carbonDioxideValue}. Temperature: ${temperatureValue}. Presence: ${presence}.`);
72 |
73 | // log, notify and set parent space computed value
74 | setSpaceValue(parentSpace.Id, spaceAvailFresh, noAlert);
75 | }
76 | }
77 | catch (error)
78 | {
79 | log(`An error has occurred processing the UDF Error: ${error.name} Message ${error.message}.`);
80 | }
81 | }
82 | function getFloatValue(str) {
83 | if(!str) {
84 | return null;
85 | }
86 | return parseFloat(str);
87 | }
88 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/actions/userDefinedFunctions/multiplemotionsensors.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | // Sample code for detecting motion in an area with multiple motion sensors:
5 | // This code is designed to work with the existing sample code.
6 | // To test this end to end, make the following changes:
7 | // 1) In the provisionSample.yaml, change the "script" key to point to this file (multiplemotionsensors.js).
8 | // 2) In the provisionSample.yaml, add a couple more motion sensors. For example, the sample "sensors" node could look like this:
9 | // sensors:
10 | // - dataType: Motion
11 | // hardwareId: SAMPLE_SENSOR_MOTION_ONE
12 | // - dataType: CarbonDioxide
13 | // hardwareId: SAMPLE_SENSOR_CARBONDIOXIDE
14 | // - dataType: Motion
15 | // hardwareId: SAMPLE_SENSOR_MOTION_TWO
16 | // - dataType: Motion
17 | // hardwareId: SAMPLE_SENSOR_MOTION_THREE
18 | // 3) Add these additional motion sensors to the device-connectivity project's appSettings.json. For example:
19 | // "Sensors": [{
20 | // "DataType": "Motion",
21 | // "HardwareId": "SAMPLE_SENSOR_MOTION_ONE"
22 | // },{
23 | // "DataType": "CarbonDioxide",
24 | // "HardwareId": "SAMPLE_SENSOR_CARBONDIOXIDE"
25 | // },{
26 | // "DataType": "Motion",
27 | // "HardwareId": "SAMPLE_SENSOR_MOTION_TWO"
28 | // },{
29 | // "DataType": "Motion",
30 | // "HardwareId": "SAMPLE_SENSOR_MOTION_THREE"
31 | // }]
32 | //
33 | //
34 |
35 |
36 | var motionType = "Motion";
37 | var occupancyStatus = "AvailableAndFresh";
38 |
39 | function process(telemetry, executionContext) {
40 | try {
41 | // Log SensorId and Message
42 | log(`Sensor ID: ${telemetry.SensorId}. `);
43 | log(`Sensor value: ${JSON.stringify(telemetry.Message)}.`);
44 |
45 | // Get sensor metadata
46 | var sensor = getSensorMetadata(telemetry.SensorId);
47 |
48 | // Retrieve the sensor reading
49 | var parseReading = JSON.parse(telemetry.Message);
50 |
51 | // Set the sensor reading as the current value for the sensor.
52 | setSensorValue(telemetry.SensorId, sensor.DataType, parseReading.SensorValue);
53 |
54 | // Get parent space
55 | var parentSpace = sensor.Space();
56 |
57 | // Get all children sensors of the parent space
58 | var allSensors = parentSpace.ChildSensors();
59 |
60 | // Get all motion sensors of the parent space
61 | var motionSensors = allSensors.filter(function(item){
62 | return item.DataType === motionType;
63 | });
64 |
65 | // Is any of the motion sensors detecting motion?
66 | var motionDetected = motionSensors.find(function(element) {
67 | return element.Value().Value.toLowerCase() === "true";
68 | });
69 |
70 | // Set OccupancyStatus for the space
71 | var occupied = "Room is occupied";
72 | var empty = "Room is empty!";
73 |
74 | if (motionDetected != undefined)
75 | {
76 | // Motion is detected by at least one sensor
77 | log(`${occupied}. Motion Detected: ${motionDetected}. Sensors: ${motionSensors}.`);
78 | setSpaceValue(parentSpace.Id, occupancyStatus, occupied);
79 | }
80 | else {
81 | // No motion detected by any sensor
82 | log(`${empty}. Motion Not Detected: ${motionDetected}. Sensors: ${motionSensors}.`);
83 | setSpaceValue(parentSpace.Id, occupancyStatus, empty);
84 | // You could try creating a Logic App mail notification in case none of the motion sensors
85 | // detect anything. In that case, uncomment the following line:
86 | // parentSpace.Notify(JSON.stringify(empty));
87 | }
88 | }
89 | catch (error)
90 | {
91 | var errormsg = `An error has occurred processing the UDF Error: ${error.name} Message ${error.message}.`;
92 | log(errormsg);
93 | setSpaceValue(parentSpace.Id, occupancyStatus, errormsg);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/api/README.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | This folder contains helpers that wrap Digital Twins REST API to produce and consume in memory [models](../models/README.md). Additional helpers are supplied to further simplify API interactions.
4 |
5 | Please note that the supplied API helpers demonstrate only a few of the full range of API capabilities.
6 |
7 | ## Swagger
8 |
9 | See the Swagger documentation that is part of your Azure Digital Twins instance for a complete description of API capabilities:
10 |
11 | A Swagger sneak preview is provided to demonstrate the API feature set. It's hosted at [docs.westcentralus.azuresmartspaces.net/management/swagger](https://docs.westcentralus.azuresmartspaces.net/management/swagger).
12 |
13 | You can access your own, generated, Management API Swagger documentation at:
14 |
15 | ```plaintext
16 | https://yourInstanceName.yourLocation.azuresmartspaces.net/management/swagger
17 | ```
18 |
19 | * Replace `yourInstanceName` with the name of your Azure Digital Twins instance.
20 | * Replace `yourLocation` with which server region your instance is hosted on.
21 |
22 | ## AutoRest
23 |
24 | Also see [AutoRest](https://github.com/Azure/autorest) for how you can generate full C# models and APIs from that documentation.
25 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/api/create.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Net.Http.Headers;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Logging;
10 | using Newtonsoft.Json;
11 |
12 | namespace Microsoft.Azure.DigitalTwins.Samples
13 | {
14 | public partial class Api
15 | {
16 | public static async Task CreateDevice(HttpClient httpClient, ILogger logger, Models.DeviceCreate deviceCreate)
17 | {
18 | logger.LogInformation($"Creating Device: {JsonConvert.SerializeObject(deviceCreate, Formatting.Indented)}");
19 | var content = JsonConvert.SerializeObject(deviceCreate);
20 | var response = await httpClient.PostAsync("devices", new StringContent(content, Encoding.UTF8, "application/json"));
21 | return await GetIdFromResponse(response, logger);
22 | }
23 |
24 | public static async Task CreateEndpoints(HttpClient httpClient, ILogger logger, Models.EndpointsCreate endpointCreate)
25 | {
26 | logger.LogInformation($"Creating Endpoint: {JsonConvert.SerializeObject(endpointCreate, Formatting.Indented)}");
27 | var content = JsonConvert.SerializeObject(endpointCreate);
28 | var response = await httpClient.PostAsync("endpoints", new StringContent(content, Encoding.UTF8, "application/json"));
29 | return await GetIdFromResponse(response, logger);
30 | }
31 |
32 | public static async Task CreateKeyStore(HttpClient httpClient, ILogger logger, Models.KeyStoreCreate keyStoreCreate)
33 | {
34 | logger.LogInformation($"Creating KeyStore: {JsonConvert.SerializeObject(keyStoreCreate, Formatting.Indented)}");
35 | var content = JsonConvert.SerializeObject(keyStoreCreate);
36 | var response = await httpClient.PostAsync("keystores", new StringContent(content, Encoding.UTF8, "application/json"));
37 | return await GetIdFromResponse(response, logger);
38 | }
39 |
40 | public static async Task CreateMatcher(HttpClient httpClient, ILogger logger, Models.MatcherCreate matcherCreate)
41 | {
42 | logger.LogInformation($"Creating Matcher: {JsonConvert.SerializeObject(matcherCreate, Formatting.Indented)}");
43 | var content = JsonConvert.SerializeObject(matcherCreate);
44 | var response = await httpClient.PostAsync("matchers", new StringContent(content, Encoding.UTF8, "application/json"));
45 | return await GetIdFromResponse(response, logger);
46 | }
47 |
48 | public static async Task CreateResource(HttpClient httpClient, ILogger logger, Models.ResourceCreate resourceCreate)
49 | {
50 | logger.LogInformation($"Creating Resource: {JsonConvert.SerializeObject(resourceCreate, Formatting.Indented)}");
51 | var content = JsonConvert.SerializeObject(resourceCreate);
52 | var response = await httpClient.PostAsync("resources", new StringContent(content, Encoding.UTF8, "application/json"));
53 | return await GetIdFromResponse(response, logger);
54 | }
55 |
56 | public static async Task CreateRoleAssignment(HttpClient httpClient, ILogger logger, Models.RoleAssignmentCreate roleAssignmentCreate)
57 | {
58 | logger.LogInformation($"Creating RoleAssignment: {JsonConvert.SerializeObject(roleAssignmentCreate, Formatting.Indented)}");
59 | var content = JsonConvert.SerializeObject(roleAssignmentCreate);
60 | var response = await httpClient.PostAsync("roleassignments", new StringContent(content, Encoding.UTF8, "application/json"));
61 | return await GetIdFromResponse(response, logger);
62 | }
63 |
64 | public static async Task CreateSensor(HttpClient httpClient, ILogger logger, Models.SensorCreate sensorCreate)
65 | {
66 | logger.LogInformation($"Creating Sensor: {JsonConvert.SerializeObject(sensorCreate, Formatting.Indented)}");
67 | var content = JsonConvert.SerializeObject(sensorCreate);
68 | var response = await httpClient.PostAsync("sensors", new StringContent(content, Encoding.UTF8, "application/json"));
69 | return await GetIdFromResponse(response, logger);
70 | }
71 |
72 | public static async Task CreateSpace(HttpClient httpClient, ILogger logger, Models.SpaceCreate spaceCreate)
73 | {
74 | logger.LogInformation($"Creating Space: {JsonConvert.SerializeObject(spaceCreate, Formatting.Indented)}");
75 | var content = JsonConvert.SerializeObject(spaceCreate);
76 | var response = await httpClient.PostAsync("spaces", new StringContent(content, Encoding.UTF8, "application/json"));
77 | return await GetIdFromResponse(response, logger);
78 | }
79 |
80 | public static async Task CreateProperty(HttpClient httpClient, ILogger logger, Guid spaceId, Models.PropertyCreate propertyCreate)
81 | {
82 | logger.LogInformation($"Creating Property: {JsonConvert.SerializeObject(propertyCreate, Formatting.Indented)}");
83 | var content = JsonConvert.SerializeObject(propertyCreate);
84 | var response = await httpClient.PostAsync($"spaces/{spaceId.ToString()}/properties", new StringContent(content, Encoding.UTF8, "application/json"));
85 | logger.LogInformation($"Creating Property Response: {response}");
86 | }
87 |
88 | public static async Task CreatePropertyKey(HttpClient httpClient, ILogger logger, Models.PropertyKeyCreate propertyKeyCreate)
89 | {
90 | logger.LogInformation($"Creating PropertyKey: {JsonConvert.SerializeObject(propertyKeyCreate, Formatting.Indented)}");
91 | var content = JsonConvert.SerializeObject(propertyKeyCreate);
92 | var response = await httpClient.PostAsync($"propertykeys", new StringContent(content, Encoding.UTF8, "application/json"));
93 | logger.LogInformation($"Creating PropertyKey Response: {response}");
94 | }
95 |
96 | public static async Task CreateUserDefinedFunction(
97 | HttpClient httpClient,
98 | ILogger logger,
99 | Models.UserDefinedFunctionCreate userDefinedFunctionCreate,
100 | string js)
101 | {
102 | logger.LogInformation($"Creating UserDefinedFunction with Metadata: {JsonConvert.SerializeObject(userDefinedFunctionCreate, Formatting.Indented)}");
103 | var displayContent = js.Length > 100 ? js.Substring(0, 100) + "..." : js;
104 | logger.LogInformation($"Creating UserDefinedFunction with Content: {displayContent}");
105 |
106 | var metadataContent = new StringContent(JsonConvert.SerializeObject(userDefinedFunctionCreate), Encoding.UTF8, "application/json");
107 | metadataContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
108 |
109 | var multipartContent = new MultipartFormDataContent("userDefinedFunctionBoundary");
110 | multipartContent.Add(metadataContent, "metadata");
111 | multipartContent.Add(new StringContent(js), "contents");
112 |
113 | var response = await httpClient.PostAsync("userdefinedfunctions", multipartContent);
114 | return await GetIdFromResponse(response, logger);
115 | }
116 |
117 | private static async Task GetIdFromResponse(HttpResponseMessage response, ILogger logger)
118 | {
119 | if (!response.IsSuccessStatusCode)
120 | return Guid.Empty;
121 |
122 | var content = await response.Content.ReadAsStringAsync();
123 |
124 | // strip out the double quotes that come in the response and parse into a guid
125 | if (!Guid.TryParse(content.Substring(1, content.Length - 2), out var createdId))
126 | {
127 | logger.LogError($"Returned value from POST did not parse into a guid: {content}");
128 | return Guid.Empty;
129 | }
130 |
131 | return createdId;
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/api/find.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Logging;
10 | using Newtonsoft.Json;
11 |
12 | namespace Microsoft.Azure.DigitalTwins.Samples
13 | {
14 | public partial class Api
15 | {
16 | // Returns a device with same hardwareId and spaceId if there is exactly one.
17 | // Otherwise returns null.
18 | public static async Task FindDevice(
19 | HttpClient httpClient,
20 | ILogger logger,
21 | string hardwareId,
22 | Guid? spaceId,
23 | string includes = null)
24 | {
25 | var filterHardwareIds = $"hardwareIds={hardwareId}";
26 | var filterSpaceId = spaceId != null ? $"&spaceIds={spaceId.ToString()}" : "";
27 | var includesParam = includes != null ? $"&includes={includes}" : "";
28 | var filter = $"{filterHardwareIds}{filterSpaceId}{includesParam}";
29 |
30 | var response = await httpClient.GetAsync($"devices?{filter}");
31 | if (response.IsSuccessStatusCode)
32 | {
33 | var content = await response.Content.ReadAsStringAsync();
34 | var devices = JsonConvert.DeserializeObject>(content);
35 | var matchingDevice = devices.SingleOrDefault();
36 | if (matchingDevice != null)
37 | {
38 | logger.LogInformation($"Retrieved Unique Device using 'hardwareId' and 'spaceId': {JsonConvert.SerializeObject(matchingDevice, Formatting.Indented)}");
39 | return matchingDevice;
40 | }
41 | }
42 | return null;
43 | }
44 |
45 | // Returns a matcher with same name and spaceId if there is exactly one.
46 | // Otherwise returns null.
47 | public static async Task> FindMatchers(
48 | HttpClient httpClient,
49 | ILogger logger,
50 | IEnumerable names,
51 | Guid spaceId)
52 | {
53 | var commaDelimitedNames = names.Aggregate((string acc, string s) => acc + "," + s);
54 | var filterNames = $"names={commaDelimitedNames}";
55 | var filterSpaceId = $"&spaceIds={spaceId.ToString()}";
56 | var filter = $"{filterNames}{filterSpaceId}";
57 |
58 | var response = await httpClient.GetAsync($"matchers?{filter}");
59 | if (response.IsSuccessStatusCode)
60 | {
61 | var content = await response.Content.ReadAsStringAsync();
62 | var matchers = JsonConvert.DeserializeObject>(content);
63 | if (matchers != null)
64 | {
65 | logger.LogInformation($"Retrieved Unique Matchers using 'names' and 'spaceId': {JsonConvert.SerializeObject(matchers, Formatting.Indented)}");
66 | return matchers;
67 | }
68 | }
69 | return null;
70 | }
71 |
72 | // Returns a space with same name and parentId if there is exactly one
73 | // that maches that criteria. Otherwise returns null.
74 | public static async Task FindSpace(
75 | HttpClient httpClient,
76 | ILogger logger,
77 | string name,
78 | Guid parentId)
79 | {
80 | var filterName = $"Name eq '{name}'";
81 | var filterParentSpaceId = parentId != Guid.Empty
82 | ? $"ParentSpaceId eq guid'{parentId}'"
83 | : $"ParentSpaceId eq null";
84 | var odataFilter = $"$filter={filterName} and {filterParentSpaceId}";
85 |
86 | var response = await httpClient.GetAsync($"spaces?{odataFilter}");
87 | if (response.IsSuccessStatusCode)
88 | {
89 | var content = await response.Content.ReadAsStringAsync();
90 | var spaces = JsonConvert.DeserializeObject>(content);
91 | var matchingSpace = spaces.SingleOrDefault();
92 | if (matchingSpace != null)
93 | {
94 | logger.LogInformation($"Retrieved Unique Space using 'name' and 'parentSpaceId': {JsonConvert.SerializeObject(matchingSpace, Formatting.Indented)}");
95 | return matchingSpace;
96 | }
97 | }
98 | return null;
99 | }
100 |
101 | // Returns a user defined fucntion with same name and spaceId if there is exactly one.
102 | // Otherwise returns null.
103 | public static async Task FindUserDefinedFunction(
104 | HttpClient httpClient,
105 | ILogger logger,
106 | string name,
107 | Guid spaceId)
108 | {
109 | var filterNames = $"names={name}";
110 | var filterSpaceId = $"&spaceIds={spaceId.ToString()}";
111 | var filter = $"{filterNames}{filterSpaceId}";
112 |
113 | var response = await httpClient.GetAsync($"userdefinedfunctions?{filter}&includes=matchers");
114 | if (response.IsSuccessStatusCode)
115 | {
116 | var content = await response.Content.ReadAsStringAsync();
117 | var userDefinedFunctions = JsonConvert.DeserializeObject>(content);
118 | var userDefinedFunction = userDefinedFunctions.SingleOrDefault();
119 | if (userDefinedFunction != null)
120 | {
121 | logger.LogInformation($"Retrieved Unique UserDefinedFunction using 'name' and 'spaceId': {JsonConvert.SerializeObject(userDefinedFunction, Formatting.Indented)}");
122 | return userDefinedFunction;
123 | }
124 | }
125 | return null;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/api/get.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using Microsoft.Extensions.Logging;
11 | using Newtonsoft.Json;
12 |
13 | namespace Microsoft.Azure.DigitalTwins.Samples
14 | {
15 | public partial class Api
16 | {
17 | public static async Task> GetOntologies(
18 | HttpClient httpClient,
19 | ILogger logger)
20 | {
21 | var response = await httpClient.GetAsync($"ontologies");
22 | if (response.IsSuccessStatusCode)
23 | {
24 | var content = await response.Content.ReadAsStringAsync();
25 | var ontologies = JsonConvert.DeserializeObject>(content);
26 | logger.LogInformation($"Retrieved Ontologies: {JsonConvert.SerializeObject(ontologies, Formatting.Indented)}");
27 | return ontologies;
28 | }
29 | else
30 | {
31 | return Array.Empty();
32 | }
33 | }
34 |
35 | public static async Task> GetPropertyKeys(
36 | HttpClient httpClient,
37 | ILogger logger)
38 | {
39 | var response = await httpClient.GetAsync($"propertykeys");
40 | if (response.IsSuccessStatusCode)
41 | {
42 | var content = await response.Content.ReadAsStringAsync();
43 | var propertyKeys = JsonConvert.DeserializeObject>(content);
44 | logger.LogInformation($"Retrieved PropertyKeys: {JsonConvert.SerializeObject(propertyKeys, Formatting.Indented)}");
45 | return propertyKeys;
46 | }
47 | else
48 | {
49 | return Array.Empty();
50 | }
51 | }
52 |
53 | public static async Task GetResource(
54 | HttpClient httpClient,
55 | ILogger logger,
56 | Guid id)
57 | {
58 | if (id == Guid.Empty)
59 | throw new ArgumentException("GetResource requires a non empty guid as id");
60 |
61 | var response = await httpClient.GetAsync($"resources/{id}");
62 | if (response.IsSuccessStatusCode)
63 | {
64 | var content = await response.Content.ReadAsStringAsync();
65 | var resource = JsonConvert.DeserializeObject(content);
66 | logger.LogInformation($"Retrieved Resource: {JsonConvert.SerializeObject(resource, Formatting.Indented)}");
67 | return resource;
68 | }
69 |
70 | return null;
71 | }
72 |
73 | public static async Task GetSpace(
74 | HttpClient httpClient,
75 | ILogger logger,
76 | Guid id,
77 | string includes = null)
78 | {
79 | if (id == Guid.Empty)
80 | throw new ArgumentException("GetSpace requires a non empty guid as id");
81 |
82 | var response = await httpClient.GetAsync($"spaces/{id}/" + (includes != null ? $"?includes={includes}" : ""));
83 | if (response.IsSuccessStatusCode)
84 | {
85 | var content = await response.Content.ReadAsStringAsync();
86 | var space = JsonConvert.DeserializeObject(content);
87 | logger.LogInformation($"Retrieved Space: {JsonConvert.SerializeObject(space, Formatting.Indented)}");
88 | return space;
89 | }
90 |
91 | return null;
92 | }
93 |
94 | public static async Task GetDevice(
95 | HttpClient httpClient,
96 | ILogger logger,
97 | Guid id,
98 | string includes = null)
99 | {
100 | if (id == Guid.Empty)
101 | throw new ArgumentException("GetDevice requires a non empty guid as id");
102 |
103 | var response = await httpClient.GetAsync($"devices/{id}/" + (includes != null ? $"?includes={includes}" : ""));
104 | if (response.IsSuccessStatusCode)
105 | {
106 | var content = await response.Content.ReadAsStringAsync();
107 | var device = JsonConvert.DeserializeObject(content);
108 | logger.LogInformation($"Retrieved Device: {JsonConvert.SerializeObject(device, Formatting.Indented)}");
109 | return device;
110 | }
111 |
112 | return null;
113 | }
114 |
115 | public static async Task> GetSpaces(
116 | HttpClient httpClient,
117 | ILogger logger,
118 | int maxNumberToGet = 10,
119 | string includes = null,
120 | string propertyKey = null)
121 | {
122 | var includesFilter = (includes != null ? $"includes={includes}" : "");
123 | var propertyKeyFilter = (propertyKey != null ? $"propertyKey={propertyKey}" : "");
124 | var topFilter = $"$top={maxNumberToGet}";
125 | var response = await httpClient.GetAsync($"spaces{MakeQueryParams(new [] {includesFilter, propertyKeyFilter, topFilter})}");
126 | if (response.IsSuccessStatusCode)
127 | {
128 | var content = await response.Content.ReadAsStringAsync();
129 | var spaces = JsonConvert.DeserializeObject>(content);
130 | logger.LogInformation($"Retrieved {spaces.Count()} Spaces");
131 | return spaces;
132 | }
133 | else
134 | {
135 | return Array.Empty();
136 | }
137 | }
138 |
139 | public static async Task> GetSensorsOfSpace(
140 | HttpClient httpClient,
141 | ILogger logger,
142 | Guid spaceId)
143 | {
144 | var response = await httpClient.GetAsync($"sensors?spaceId={spaceId.ToString()}&includes=Types");
145 | if (response.IsSuccessStatusCode)
146 | {
147 | var content = await response.Content.ReadAsStringAsync();
148 | var sensors = JsonConvert.DeserializeObject>(content);
149 | logger.LogInformation($"Retrieved {sensors.Count()} Sensors");
150 | return sensors;
151 | }
152 | else
153 | {
154 | return Array.Empty();
155 | }
156 | }
157 |
158 | private static string MakeQueryParams(IEnumerable queryParams)
159 | {
160 | return queryParams
161 | .Where(s => !string.IsNullOrWhiteSpace(s))
162 | .Select((s, i) => (i == 0 ? '?' : '&') + s)
163 | .Aggregate((result, cur) => result + cur);
164 | }
165 | }
166 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/api/isResourceProvisioning.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Microsoft.Extensions.Logging;
9 | using Newtonsoft.Json;
10 |
11 | namespace Microsoft.Azure.DigitalTwins.Samples
12 | {
13 | public partial class Api
14 | {
15 | public static async Task IsResourceProvisioning(
16 | HttpClient httpClient,
17 | ILogger logger,
18 | Guid id)
19 | {
20 | var resource = await GetResource(httpClient, logger, id);
21 | if (resource == null)
22 | {
23 | logger.LogError($"Failed to find expected resource, {id.ToString()}");
24 | return false;
25 | }
26 |
27 | return resource.Status.ToLower() == "provisioning";
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/api/update.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Net.Http.Headers;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using Microsoft.Extensions.Logging;
10 | using Newtonsoft.Json;
11 |
12 | namespace Microsoft.Azure.DigitalTwins.Samples
13 | {
14 | public partial class Api
15 | {
16 | public static async Task UpdateUserDefinedFunction(
17 | HttpClient httpClient,
18 | ILogger logger,
19 | Models.UserDefinedFunctionUpdate userDefinedFunction,
20 | string js)
21 | {
22 | logger.LogInformation($"Updating UserDefinedFunction with Metadata: {JsonConvert.SerializeObject(userDefinedFunction, Formatting.Indented)}");
23 | var displayContent = js.Length > 100 ? js.Substring(0, 100) + "..." : js;
24 | logger.LogInformation($"Updating UserDefinedFunction with Content: {displayContent}");
25 |
26 | var metadataContent = new StringContent(JsonConvert.SerializeObject(userDefinedFunction), Encoding.UTF8, "application/json");
27 | metadataContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
28 |
29 | var multipartContent = new MultipartFormDataContent("userDefinedFunctionBoundary");
30 | multipartContent.Add(metadataContent, "metadata");
31 | multipartContent.Add(new StringContent(js), "contents");
32 |
33 | await httpClient.PatchAsync($"userdefinedfunctions/{userDefinedFunction.Id}", multipartContent);
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/appSettings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.IO;
6 | using Microsoft.Extensions.Configuration;
7 |
8 | namespace Microsoft.Azure.DigitalTwins.Samples
9 | {
10 | public class AppSettings {
11 | // Note: this is a constant because it is the same for every user authorizing
12 | // against the Digital Twins Apis
13 | private static string DigitalTwinsAppId = "0b07f429-9f4b-4714-9392-cc5e8e80c8b0";
14 | private static string[] AadScopes = new string[] { DigitalTwinsAppId + "/Read.Write" };
15 |
16 | public string AADInstance { get; set; }
17 | public string AadRedirectUri { get; set; }
18 | public string ClientId { get; set; }
19 | public string ClientSecret { get; set; }
20 | public string Resource { get; set; } = DigitalTwinsAppId;
21 | public string Tenant { get; set; }
22 | public string BaseUrl { get; set; }
23 | public string Authority => AADInstance + Tenant;
24 | public string[] Scopes { get; set; } = AadScopes;
25 |
26 | public static AppSettings Load()
27 | {
28 | var appSettings = new ConfigurationBuilder()
29 | .SetBasePath(Directory.GetCurrentDirectory())
30 | .AddJsonFile("appSettings.json")
31 | .AddJsonFile("appSettings.dev.json", optional: true)
32 | .Build()
33 | .Get();
34 |
35 | // Sanitize input
36 |
37 | // This is because httpClient will behave differently if the
38 | // passed in Uri has a trailing slash when using GetAsync.
39 | appSettings.BaseUrl = EnsureTrailingSlash(appSettings.BaseUrl);
40 |
41 | return appSettings;
42 | }
43 |
44 | private static string EnsureTrailingSlash(string baseUrl)
45 | => baseUrl.Length == 0 || baseUrl[baseUrl.Length-1] == '/'
46 | ? baseUrl
47 | : baseUrl + '/';
48 | }
49 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/appSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "AADInstance": "https://login.microsoftonline.com/",
3 | "AadRedirectUri": "http://localhost:8080/",
4 | "ClientId": "",
5 | "Tenant": "",
6 | "BaseUrl": "https://..azuresmartspaces.net/management/api/v1.0/"
7 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/auth.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Threading.Tasks;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Identity.Client;
8 | using System.Net.Http;
9 |
10 | namespace Microsoft.Azure.DigitalTwins.Samples
11 | {
12 | internal static class Authentication
13 | {
14 | // Gets an access token
15 | // First tries (by making a request) using a cached token and if that
16 | // fails we generated a new one using device login and cache it.
17 | internal static async Task GetToken(AppSettings appSettings)
18 | {
19 | var accessTokenFilename = ".accesstoken";
20 | var accessToken = ReadAccessTokenFromFile(accessTokenFilename);
21 | if (accessToken == null || !(await TryRequestWithAccessToken(new Uri(appSettings.BaseUrl), accessToken)))
22 | {
23 | accessToken = await Authentication.GetNewToken(appSettings);
24 | System.IO.File.WriteAllText(accessTokenFilename, accessToken);
25 | }
26 |
27 | return accessToken;
28 | }
29 |
30 | private static async Task TryRequestWithAccessToken(Uri baseAddress, string accessToken)
31 | {
32 | // We create a new httpClient so we can force console logging for this operation
33 | var httpClient = new HttpClient(new LoggingHttpHandler(Loggers.ConsoleLogger))
34 | {
35 | BaseAddress = baseAddress,
36 | };
37 | httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
38 |
39 | Loggers.ConsoleLogger.LogInformation("Checking if previous access token is valid...");
40 |
41 | return (await httpClient.GetAsync("ontologies")).IsSuccessStatusCode;
42 | }
43 |
44 | private static string ReadAccessTokenFromFile(string filename)
45 | => System.IO.File.Exists(filename) ? System.IO.File.ReadAllText(filename) : null;
46 |
47 | // MSAL.NET configuration. Review the product documentation for more information about MSAL.NET authentication options.
48 | // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/
49 | private static async Task GetNewToken(AppSettings appSettings)
50 | {
51 | IPublicClientApplication app = PublicClientApplicationBuilder
52 | .Create(appSettings.ClientId)
53 | .WithRedirectUri(appSettings.AadRedirectUri)
54 | .WithAuthority(appSettings.Authority)
55 | .Build();
56 |
57 | AuthenticationResult result = await app
58 | .AcquireTokenInteractive(appSettings.Scopes)
59 | .ExecuteAsync();
60 |
61 | Console.WriteLine("");
62 | Console.WriteLine("MSAL Authentication Token Acquired: {0}", result.AccessToken);
63 | Console.WriteLine("");
64 | return result.AccessToken;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/loggers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples
7 | {
8 | public static class Loggers
9 | {
10 | public static ILogger SilentLogger =
11 | new Microsoft.Extensions.Logging.LoggerFactory()
12 | .CreateLogger("DigitalTwinsQuickstart");
13 | public static ILogger ConsoleLogger =
14 | new Microsoft.Extensions.Logging.LoggerFactory()
15 | .AddConsole(LogLevel.Trace)
16 | .CreateLogger("DigitalTwinsQuickstart");
17 | }
18 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/loggingHttpHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Net.Http;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using Microsoft.Extensions.Logging;
12 | using Newtonsoft.Json;
13 |
14 | namespace Microsoft.Azure.DigitalTwins.Samples
15 | {
16 | public class LoggingHttpHandler : DelegatingHandler
17 | {
18 | private ILogger logger;
19 |
20 | public LoggingHttpHandler(ILogger logger)
21 | : base(new HttpClientHandler())
22 | {
23 | this.logger = logger;
24 | }
25 |
26 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
27 | {
28 | LogRequest(request);
29 |
30 | var response = await base.SendAsync(request, cancellationToken);
31 |
32 | await LogResponse(response);
33 |
34 | return response;
35 | }
36 |
37 | private void LogRequest(HttpRequestMessage request)
38 | {
39 | logger.LogTrace($"Request: {request.Method} {request.RequestUri}");
40 |
41 | // logger.LogDebug($"More Info: {Serialize(request)}");
42 | }
43 |
44 | private async Task LogResponse(HttpResponseMessage response)
45 | {
46 | const int maxContentLength = 200;
47 | var content = await response.Content?.ReadAsStringAsync();
48 |
49 | var statusCode = (int)response.StatusCode;
50 |
51 | // Truncate responses if they are successful.
52 | if (statusCode < 400)
53 | {
54 | var contentMaxLength = content == null || content.Length < maxContentLength
55 | ? content
56 | : content.Substring(0, maxContentLength - 3) + "...";
57 |
58 | content = contentMaxLength == null ? "" : $", {contentMaxLength}";
59 | }
60 |
61 | logger.LogTrace($"Response Status: {(int)response.StatusCode}, {response.StatusCode} {content}");
62 |
63 | // Enable to get more details:
64 | // logger.LogTrace($"Full Response: {Serialize(response)}");
65 | // logger.LogTrace($"Full Response Content: {content}");
66 | }
67 |
68 | private static string Serialize(object o)
69 | => JsonConvert.SerializeObject(o, Formatting.Indented);
70 | }
71 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/README.md:
--------------------------------------------------------------------------------
1 | # Models
2 |
3 | This folder contains models used when interacting with Azure Digital Twins Management APIs.
4 |
5 | Note that the supplied models demonstrate only a limited number of the total available fields.
6 |
7 | ## Swagger
8 |
9 | See the included Swagger documentation to see all available models and fields:
10 |
11 | A Swagger sneak preview is provided to demonstrate the API feature set. It's hosted at [docs.westcentralus.azuresmartspaces.net/management/swagger](https://docs.westcentralus.azuresmartspaces.net/management/swagger).
12 |
13 | You can access your own, generated, Management API Swagger documentation at:
14 |
15 | ```plaintext
16 | https://yourInstanceName.yourLocation.azuresmartspaces.net/management/swagger
17 | ```
18 |
19 | * Replace `yourInstanceName` with the name of your Azure Digital Twins instance.
20 | * Replace `yourLocation` with which server region your instance is hosted on.
21 |
22 | ## AutoRest
23 |
24 | Also see [AutoRest](https://github.com/Azure/autorest) to learn how to generate full C# models from that documentation.
25 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/device.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | // TODO: Need to fixup ids in models to be Guids (were approriate)
7 | // Then we can change upstream apis like DescriptionExtensions
8 | public class Device
9 | {
10 | public string ConnectionString { get; set; }
11 | public string HardwareId { get; set; }
12 | public string Id { get; set; }
13 | public string Name { get; set; }
14 | public string SpaceId { get; set; }
15 | }
16 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/deviceCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class DeviceCreate
7 | {
8 | public string HardwareId { get; set; }
9 | public string Name { get; set; }
10 | public string SpaceId { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/endpointsCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class EndpointsCreate
7 | {
8 | public string ConnectionString { get; set; }
9 | public string[] EventTypes { get; set; }
10 | public string Path { get; set; }
11 | public string SecondaryConnectionString { get; set; }
12 | public string Type { get; set; }
13 | }
14 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/historicalValues.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class HistoricalValues
9 | {
10 | public string Description;
11 | public string Value;
12 | }
13 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/keyStoreCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class KeyStoreCreate
7 | {
8 | public string Name { get; set; }
9 | public string SpaceId { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/matcher.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class Matcher
9 | {
10 | public string Id { get; set; }
11 | public string Name { get; set; }
12 | public string SpaceId { get; set; }
13 | }
14 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/matcherCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class ConditionCreate
9 | {
10 | public string Target { get; set; }
11 | public string Path { get; set; }
12 | public string Value { get; set; }
13 | public string Comparison { get; set; }
14 | }
15 |
16 | public class MatcherCreate
17 | {
18 | public string Name { get; set; }
19 | public string SpaceId { get; set; }
20 | public IEnumerable Conditions { get; set; }
21 | }
22 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/ontology.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class Ontology
7 | {
8 | public int Id {get; set;}
9 | public string Name { get; set; }
10 | public bool Loaded { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/property.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class Property
7 | {
8 | public string DataType { get; set; }
9 | public string Name { get; set; }
10 | public string Value { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/propertyCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class PropertyCreate
7 | {
8 | public string Name { get; set; }
9 | public string Value { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/propertyKeyCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class PropertyKeyCreate
9 | {
10 | public string Name { get; set; }
11 | public string Scope { get; set; }
12 | public Guid SpaceId { get; set; }
13 | }
14 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/propertyKeys.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class PropertyKeys
7 | {
8 | public string Id { get; set; }
9 | public string Name { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/resource.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class Resource
7 | {
8 | public string Id { get; set; }
9 | public string Status { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/resourceCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class ResourceCreate
7 | {
8 | public string SpaceId { get; set; }
9 | public string Type { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/roleAssignmentCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class RoleAssignmentCreate
9 | {
10 | public string ObjectId { get; set; }
11 | public string ObjectIdType { get; set; }
12 | public string Path { get; set; }
13 | public string RoleId { get; set; }
14 | public string TenantId { get; set; }
15 | }
16 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/sensor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class Sensor
7 | {
8 | public string DataType { get; set; }
9 | public string DeviceId { get; set; }
10 | public string HardwareId { get; set; }
11 | public string Id { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/sensorCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class SensorCreate
7 | {
8 | public string DataType { get; set; }
9 | public string DeviceId { get; set; }
10 | public string HardwareId { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/sensorValues.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class SpaceValue
9 | {
10 | public string Type;
11 | public string Value;
12 | public string Timestamp;
13 | public IEnumerable HistoricalValues;
14 | }
15 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/space.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class Space
9 | {
10 | public string Id { get; set; }
11 | public string Name { get; set; }
12 | public string FriendlyName { get; set; }
13 | public string Type { get; set; }
14 | public int TypeId { get; set; }
15 | public string ParentSpaceId { get; set; }
16 | public string Subtype { get; set; }
17 | public int SubtypeId { get; set; }
18 | public string Status { get; set; }
19 | public int StatusId { get; set; }
20 | public IEnumerable Properties { get; set; }
21 | public IEnumerable Children { get; set; }
22 | public IEnumerable SpacePaths { get; set; }
23 | public IEnumerable Values { get; set; }
24 | }
25 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/spaceCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
5 | {
6 | public class SpaceCreate
7 | {
8 | public string Name { get; set; }
9 | public string Type { get; set; }
10 | public string ParentSpaceId { get; set; }
11 | public string Subtype { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/userDefinedFunction.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class UserDefinedFunction
9 | {
10 | public string Id { get; set; }
11 | public IEnumerable Matchers { get; set; }
12 | public string Name { get; set; }
13 | public string SpaceId { get; set; }
14 | }
15 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/userDefinedFunctionCreate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class UserDefinedFunctionCreate
9 | {
10 | public IEnumerable Matchers { get; set; }
11 | public string Name { get; set; }
12 | public string SpaceId { get; set; }
13 | }
14 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/models/userDefinedFunctionUpdate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft.Azure.DigitalTwins.Samples.Models
7 | {
8 | public class UserDefinedFunctionUpdate
9 | {
10 | public string Id { get; set; }
11 | public IEnumerable Matchers { get; set; }
12 | public string Name { get; set; }
13 | public string SpaceId { get; set; }
14 | }
15 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/src/occupancyQuickstart.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.1
6 | latest
7 | Microsoft.Azure.DigitalTwins.Samples
8 | $(MSBuildThisFileDirectory)
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/occupancy-quickstart/src/program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Net;
9 | using System.Net.Http;
10 | using System.Text;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 | using Microsoft.Extensions.Configuration;
14 | using Newtonsoft.Json;
15 | using YamlDotNet.Serialization;
16 | using Microsoft.Extensions.Logging;
17 | using Microsoft.Identity.Client;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples
20 | {
21 | static class Program
22 | {
23 | static async Task Main(string[] args)
24 | {
25 | try
26 | {
27 | var appSettings = AppSettings.Load();
28 |
29 | var actionName = ParseArgs(args);
30 | if (actionName == null)
31 | return;
32 |
33 | switch (actionName)
34 | {
35 | case ActionName.CreateEndpoints:
36 | await Actions.CreateEndpoints(await SetupHttpClient(Loggers.ConsoleLogger, appSettings), Loggers.ConsoleLogger);
37 | break;
38 | case ActionName.CreateRoleAssignments:
39 | await Actions.CreateRoleAssignments(await SetupHttpClient(Loggers.ConsoleLogger, appSettings), Loggers.ConsoleLogger);
40 | break;
41 | case ActionName.GetAvailableAndFreshSpaces:
42 | await Actions.GetAvailableAndFreshSpaces(await SetupHttpClient(Loggers.SilentLogger, appSettings));
43 | break;
44 | case ActionName.GetOntologies:
45 | await Api.GetOntologies(await SetupHttpClient(Loggers.ConsoleLogger, appSettings), Loggers.ConsoleLogger);
46 | break;
47 | case ActionName.GetSpaces:
48 | await Actions.GetSpaces(await SetupHttpClient(Loggers.ConsoleLogger, appSettings), Loggers.ConsoleLogger);
49 | break;
50 | case ActionName.ProvisionSample:
51 | await Actions.ProvisionSample(await SetupHttpClient(Loggers.ConsoleLogger, appSettings), Loggers.ConsoleLogger);
52 | break;
53 |
54 | default:
55 | throw new NotImplementedException();
56 | }
57 | }
58 | catch (Exception e)
59 | {
60 | Console.WriteLine($"Exception: {e}");
61 | }
62 | }
63 |
64 | private static ActionName? ParseArgs(string[] args)
65 | {
66 | if (args.Length >= 1 && Enum.TryParse(args[0], out ActionName actionName))
67 | {
68 | return actionName;
69 | }
70 | else
71 | {
72 | // Generate the list of available action names from the enum
73 | // and output them in the usage string
74 | var actionNames = Enum.GetNames(typeof(ActionName))
75 | .Aggregate((string acc, string s) => acc + " | " + s);
76 | Console.WriteLine($"Usage: dotnet run [{actionNames}]");
77 |
78 | return null;
79 | }
80 | }
81 |
82 | private static async Task SetupHttpClient(ILogger logger, AppSettings appSettings)
83 | {
84 | var httpClient = new HttpClient(new LoggingHttpHandler(logger))
85 | {
86 | BaseAddress = new Uri(appSettings.BaseUrl),
87 | };
88 |
89 | var accessToken = await Authentication.GetToken(appSettings);
90 | httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
91 | return httpClient;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/createEndpointsTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class CreateEndpointsTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid endpoint1Guid = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid endpoint2Guid = new Guid("00000000-0000-0000-0000-000000000002");
26 |
27 | [Fact]
28 | public async Task GetCreateEndpointsCreatesDescriptions()
29 | {
30 | var yaml = @"
31 | - type: Type1
32 | eventTypes:
33 | - EventType1
34 | - EventType2
35 | connectionString: connectionString1
36 | secondaryConnectionString: connectionString2
37 | path: path1
38 | ";
39 | var expectedDescriptions = new [] { new EndpointDescription()
40 | {
41 | type = "Type1",
42 | eventTypes = new [] { "EventType1", "EventType2" },
43 | connectionString = "connectionString1",
44 | secondaryConnectionString = "connectionString2",
45 | path = "path1",
46 | }};
47 | var actualDescriptions = await Actions.GetCreateEndpointsDescriptions(new StringReader(yaml));
48 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
49 | }
50 |
51 | [Fact]
52 | public async Task CreateTwoEndpoints()
53 | {
54 | var endpointGuids = new[] { endpoint1Guid, endpoint2Guid };
55 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.Create(
56 | postResponseGuids: endpointGuids
57 | );
58 |
59 | var descriptions = new [] {
60 | new EndpointDescription()
61 | {
62 | type = "Type1",
63 | eventTypes = new [] { "EventType1", "EventType2" },
64 | connectionString = "connectionString1",
65 | secondaryConnectionString = "connectionString2",
66 | path = "path1",
67 | },
68 | new EndpointDescription()
69 | {
70 | type = "TypeA",
71 | eventTypes = new [] { "EventTypeA", "EventTypeB" },
72 | connectionString = "connectionStringA",
73 | secondaryConnectionString = "connectionStringB",
74 | path = "pathA",
75 | },
76 | };
77 |
78 | Assert.Equal(endpointGuids,
79 | await Actions.CreateEndpoints(httpClient, Loggers.SilentLogger, descriptions));
80 | Assert.Equal(2, httpHandler.PostRequests["endpoints"].Count);
81 | Assert.False(httpHandler.GetRequests.ContainsKey("endpoints"));
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/createRoleAssignmentsTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class CreateRoleAssignmentsTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid roleAssignment1Guid = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid roleAssignment2Guid = new Guid("00000000-0000-0000-0000-000000000002");
26 |
27 | [Fact]
28 | public async Task GetCreateRoleAssignmentsCreatesDescriptions()
29 | {
30 | var yaml = @"
31 | - objectId: Id1
32 | objectIdType: Type1
33 | path: Path1
34 | roleId: RoleId1
35 | tenantId: TenantId1
36 | ";
37 | var expectedDescriptions = new [] { new RoleAssignmentDescription()
38 | {
39 | objectId = "Id1",
40 | objectIdType = "Type1",
41 | path = "Path1",
42 | roleId = "RoleId1",
43 | tenantId = "TenantId1",
44 | }};
45 | var actualDescriptions = await Actions.GetCreateRoleAssignmentsDescriptions(new StringReader(yaml));
46 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
47 | }
48 |
49 | [Fact]
50 | public async Task CreateTwoRoleAssignments()
51 | {
52 | var roleAssignmentGuids = new[] { roleAssignment1Guid, roleAssignment2Guid };
53 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.Create(
54 | postResponseGuids: roleAssignmentGuids
55 | );
56 |
57 | var descriptions = new [] {
58 | new RoleAssignmentDescription()
59 | {
60 | objectId = "10000000-0000-0000-0000-000000000001",
61 | objectIdType = "UserId",
62 | path = "/",
63 | roleId = "10000000-0000-0000-0000-000000000002",
64 | tenantId = "10000000-0000-0000-0000-000000000003",
65 | },
66 | new RoleAssignmentDescription()
67 | {
68 | objectId = "20000000-0000-0000-0000-000000000001",
69 | objectIdType = "UserId",
70 | path = "/",
71 | roleId = "20000000-0000-0000-0000-000000000002",
72 | tenantId = "20000000-0000-0000-0000-000000000003",
73 | },
74 | };
75 |
76 | Assert.Equal(roleAssignmentGuids,
77 | await Actions.CreateRoleAssignments(httpClient, Loggers.SilentLogger, descriptions));
78 | Assert.Equal(2, httpHandler.PostRequests["roleassignments"].Count);
79 | Assert.False(httpHandler.GetRequests.ContainsKey("roleassignments"));
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/fakeDigitalTwinsHttpClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using Newtonsoft.Json;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Net.Http;
9 | using System.Net;
10 |
11 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
12 | {
13 | public static class FakeDigitalTwinsHttpClient
14 | {
15 | public static Models.Space Space = new Models.Space()
16 | {
17 | Name = "Space1_HttpClient",
18 | Id = new Guid("90000000-0000-0000-0000-000000000001").ToString(),
19 | Type = "Space1Type",
20 | };
21 |
22 | public static Models.Device Device = new Models.Device()
23 | {
24 | Name = "Device1_HttpClient",
25 | Id = new Guid("90000000-0000-0000-0000-000000000001").ToString(),
26 | HardwareId = "DeviceHardwareId1_HttpClient",
27 | SpaceId = Space.Id,
28 | };
29 |
30 | public static (HttpClient, FakeHttpHandler) Create(
31 | IEnumerable postResponseGuids = null,
32 | IEnumerable getResponses = null)
33 | {
34 | return FakeHttpHandler.CreateHttpClient(
35 | postResponses: postResponseGuids != null ? CreateGuidResponses(postResponseGuids) : null,
36 | getResponses: getResponses);
37 | }
38 |
39 | // Creates an httpClient that will respond with a space (this.Space or passed in space)
40 | public static (HttpClient, FakeHttpHandler) CreateWithSpace(
41 | IEnumerable postResponseGuids,
42 | IEnumerable getResponses = null,
43 | IEnumerable patchResponses = null,
44 | Models.Space space = null)
45 | {
46 | postResponseGuids = postResponseGuids ?? Array.Empty();
47 | getResponses = getResponses ?? Array.Empty();
48 | patchResponses = patchResponses ?? Array.Empty();
49 | space = space ?? Space;
50 |
51 | var getRootSpaceResponse = new HttpResponseMessage()
52 | {
53 | StatusCode = HttpStatusCode.OK,
54 | Content = new StringContent(JsonConvert.SerializeObject(new [] { space })),
55 | };
56 |
57 | var getSensorsForResultsResponse = new [] { Responses.NotFound };
58 |
59 | return FakeHttpHandler.CreateHttpClient(
60 | postResponses: CreateGuidResponses(postResponseGuids),
61 | getResponses: new [] { getRootSpaceResponse }
62 | .Concat(getResponses)
63 | .Concat(getSensorsForResultsResponse),
64 | patchResponses: patchResponses);
65 | }
66 |
67 | // Creates an httpClient that will respond with a space and device
68 | // (this.Space and this.Device)
69 | public static (HttpClient, FakeHttpHandler) CreateWithDevice(
70 | IEnumerable postResponseGuids,
71 | IEnumerable getResponses = null)
72 | {
73 | getResponses = getResponses ?? Array.Empty();
74 |
75 | var getDeviceResponse = new HttpResponseMessage()
76 | {
77 | StatusCode = HttpStatusCode.OK,
78 | Content = new StringContent(JsonConvert.SerializeObject(Device)),
79 | };
80 | var getDevicesResponse = new HttpResponseMessage()
81 | {
82 | StatusCode = HttpStatusCode.OK,
83 | Content = new StringContent(JsonConvert.SerializeObject(new [] { Device })),
84 | };
85 | return CreateWithSpace(
86 | postResponseGuids: postResponseGuids,
87 | getResponses: new [] { getDevicesResponse, getDeviceResponse }.Concat(getResponses) );
88 | }
89 |
90 | private static IEnumerable CreateGuidResponses(IEnumerable guids)
91 | => guids.Select(guid => new HttpResponseMessage()
92 | {
93 | StatusCode = HttpStatusCode.OK,
94 | Content = new StringContent($"\"{guid.ToString()}\""),
95 | });
96 | }
97 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/fakeHttpHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Net.Http;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 |
13 | namespace Microsoft.Azure.DigitalTwins.Samples
14 | {
15 | // TODO: we should find something better than manually implementing this but I
16 | // haven't been able to yet:
17 | // See https://github.com/dotnet/corefx/issues/1624 for an example of non trivial discussion around this
18 | public class FakeHttpHandler : DelegatingHandler
19 | {
20 | public static (HttpClient, FakeHttpHandler) CreateHttpClient(
21 | IEnumerable postResponses = null,
22 | IEnumerable getResponses = null,
23 | IEnumerable patchResponses = null)
24 | {
25 | var httpHandler = new FakeHttpHandler()
26 | {
27 | PostResponses = postResponses,
28 | GetResponses = getResponses,
29 | PatchResponses = patchResponses,
30 | };
31 | return (
32 | new HttpClient(httpHandler)
33 | {
34 | BaseAddress = new Uri("http://bing.com"),
35 | },
36 | httpHandler);
37 | }
38 |
39 | public FakeHttpHandler()
40 | : base(new HttpClientHandler())
41 | {
42 | requests[HttpMethod.Post] = new Dictionary>();
43 | requests[HttpMethod.Get] = new Dictionary>();
44 | requests[HttpMethod.Patch] = new Dictionary>();
45 | }
46 |
47 | public IReadOnlyDictionary> PatchRequests => requests[HttpMethod.Patch];
48 | public IReadOnlyDictionary> GetRequests => requests[HttpMethod.Get];
49 | public IReadOnlyDictionary> PostRequests => requests[HttpMethod.Post];
50 | private Dictionary>> requests = new Dictionary>>();
51 |
52 | public IEnumerable PostResponses { get; set; }
53 | private IEnumerator enumeratePostResponses;
54 |
55 | public IEnumerable GetResponses { get; set; }
56 | private IEnumerator enumerateGetResponses;
57 |
58 | public IEnumerable PatchResponses { get; set; }
59 | private IEnumerator enumeratePatchResponses;
60 |
61 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
62 | {
63 | var requestName = request.RequestUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries)[0];
64 | if (!requests[request.Method].ContainsKey(requestName))
65 | requests[request.Method].Add(requestName, new List());
66 | requests[request.Method][requestName].Add(request);
67 |
68 | return Task.FromResult(GetNextResponse(ChooseResponseEnumerator(request)));
69 | }
70 |
71 | private static HttpResponseMessage GetNextResponse(IEnumerator enumerator)
72 | {
73 | if (enumerator == null || !enumerator.MoveNext())
74 | throw new InvalidOperationException("FakeHttpHandler ran out of responses");
75 | return enumerator.Current;
76 | }
77 |
78 | private IEnumerator ChooseResponseEnumerator(HttpRequestMessage request)
79 | {
80 | if (request.Method == HttpMethod.Get)
81 | {
82 | if (enumerateGetResponses == null)
83 | enumerateGetResponses = GetResponses.GetEnumerator();
84 | return enumerateGetResponses;
85 | }
86 | else if (request.Method == HttpMethod.Patch)
87 | {
88 | if (enumeratePatchResponses == null)
89 | enumeratePatchResponses = PatchResponses.GetEnumerator();
90 | return enumeratePatchResponses;
91 | }
92 | else if (request.Method == HttpMethod.Post)
93 | {
94 | if (enumeratePostResponses == null)
95 | enumeratePostResponses = PostResponses.GetEnumerator();
96 | return enumeratePostResponses;
97 | }
98 | else
99 | {
100 | throw new NotImplementedException();
101 | }
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/loggers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.Extensions.Logging;
5 | using Moq;
6 |
7 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
8 | {
9 | public static class Loggers
10 | {
11 | public static ILogger SilentLogger = new Mock().Object;
12 | public static ILogger ConsoleLogger =
13 | new Microsoft.Extensions.Logging.LoggerFactory()
14 | .AddConsole(LogLevel.Trace)
15 | .CreateLogger("DigitalTwinsQuickstartTests");
16 | }
17 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/occupancyQuickstart.tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PreserveNewest
17 |
18 |
19 | PreserveNewest
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/provisionSampleDevicesTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class ProvisionSampleDevicesTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid device1Guid = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid device2Guid = new Guid("00000000-0000-0000-0000-000000000002");
26 | private static Models.Device device1 = new Models.Device()
27 | {
28 | HardwareId = "HardwareId1",
29 | Id = device1Guid.ToString(),
30 | Name = "Device1",
31 | ConnectionString = "ConnectionString1",
32 | };
33 | private static Models.Device device2 = new Models.Device()
34 | {
35 | HardwareId = "HardwareId2",
36 | Id = device2Guid.ToString(),
37 | Name = "Device2",
38 | ConnectionString = "ConnectionString2",
39 | };
40 | private static HttpResponseMessage getDevicesResponse_device1 = new HttpResponseMessage()
41 | {
42 | StatusCode = HttpStatusCode.OK,
43 | Content = new StringContent(JsonConvert.SerializeObject(new [] { device1 })),
44 | };
45 | private static HttpResponseMessage getDeviceResponse_device1 = new HttpResponseMessage()
46 | {
47 | StatusCode = HttpStatusCode.OK,
48 | Content = new StringContent(JsonConvert.SerializeObject(device1)),
49 | };
50 | private static HttpResponseMessage getDevicesResponse_device2 = new HttpResponseMessage()
51 | {
52 | StatusCode = HttpStatusCode.OK,
53 | Content = new StringContent(JsonConvert.SerializeObject(new [] { device2 })),
54 | };
55 | private static HttpResponseMessage getDeviceResponse_device2 = new HttpResponseMessage()
56 | {
57 | StatusCode = HttpStatusCode.OK,
58 | Content = new StringContent(JsonConvert.SerializeObject(device2)),
59 | };
60 |
61 | [Fact]
62 | public async Task GetProvisionSampleCreatesDescriptions()
63 | {
64 | var yaml = @"
65 | - name: Test1
66 | devices:
67 | - name: Device1
68 | hardwareId: HardwareId1
69 | - name: Device2
70 | hardwareId: HardwareId2
71 | ";
72 | var expectedDescriptions = new [] { new SpaceDescription()
73 | {
74 | name = "Test1",
75 | devices = new [] {
76 | new DeviceDescription()
77 | {
78 | name = "Device1",
79 | hardwareId = "HardwareId1",
80 | },
81 | new DeviceDescription()
82 | {
83 | name = "Device2",
84 | hardwareId = "HardwareId2",
85 | },
86 | },
87 | }};
88 | var actualDescriptions = await Actions.GetProvisionSampleTopology(new StringReader(yaml));
89 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
90 | }
91 |
92 | [Fact]
93 | public async Task CreateTwoDevices()
94 | {
95 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
96 | postResponseGuids: new [] { device1Guid, device2Guid },
97 | getResponses: new [] { Responses.NotFound, getDeviceResponse_device1, Responses.NotFound, getDeviceResponse_device2 }
98 | );
99 |
100 | var descriptions = new [] { new SpaceDescription()
101 | {
102 | name = FakeDigitalTwinsHttpClient.Space.Name,
103 | devices = new [] {
104 | new DeviceDescription()
105 | {
106 | name = "Device1",
107 | hardwareId = "HardwareId1",
108 | },
109 | new DeviceDescription()
110 | {
111 | name = "Device2",
112 | hardwareId = "HardwareId2",
113 | }},
114 | }};
115 |
116 | var spaceResult = (await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty))
117 | .Single();
118 | Assert.Equal(2, httpHandler.PostRequests["devices"].Count);
119 | Assert.Equal(4, httpHandler.GetRequests["devices"].Count);
120 | Assert.Equal(new [] {
121 | new ProvisionResults.Device () {
122 | ConnectionString = "ConnectionString1",
123 | HardwareId = "HardwareId1",
124 | },
125 | new ProvisionResults.Device () {
126 | ConnectionString = "ConnectionString2",
127 | HardwareId = "HardwareId2",
128 | }},
129 | spaceResult.Devices);
130 | }
131 |
132 | [Fact]
133 | public async Task CreateDeviceReusesMatchingPreexistingDevice()
134 | {
135 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
136 | postResponseGuids: null,
137 | getResponses: new [] { getDevicesResponse_device1, getDeviceResponse_device1 }
138 | );
139 |
140 | var descriptions = new [] { new SpaceDescription()
141 | {
142 | name = FakeDigitalTwinsHttpClient.Space.Name,
143 | devices = new [] {
144 | new DeviceDescription()
145 | {
146 | name = device1.Name,
147 | hardwareId = device1.HardwareId,
148 | }},
149 | }};
150 |
151 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
152 | Assert.False(httpHandler.PostRequests.ContainsKey("devices"));
153 | Assert.Equal(2, httpHandler.GetRequests["devices"].Count);
154 | }
155 | }
156 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/provisionSampleMatchersTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class ProvisionSampleMatchersTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid matcher1Guid = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid matcher2Guid = new Guid("00000000-0000-0000-0000-000000000002");
26 |
27 | [Fact]
28 | public async Task GetProvisionSampleCreatesDescriptions()
29 | {
30 | var yaml = @"
31 | - name: Test1
32 | matchers:
33 | - name: Matcher1
34 | dataTypeValue: dataType1
35 | - name: Matcher2
36 | dataTypeValue: dataType2
37 | ";
38 | var expectedDescriptions = new [] { new SpaceDescription()
39 | {
40 | name = "Test1",
41 | matchers = new [] {
42 | new MatcherDescription()
43 | {
44 | name = "Matcher1",
45 | dataTypeValue = "dataType1",
46 | },
47 | new MatcherDescription()
48 | {
49 | name = "Matcher2",
50 | dataTypeValue = "dataType2",
51 | },
52 | },
53 | }};
54 | var actualDescriptions = await Actions.GetProvisionSampleTopology(new StringReader(yaml));
55 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
56 | }
57 |
58 | [Fact]
59 | public async Task CreateTwoMatchers()
60 | {
61 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
62 | postResponseGuids: new [] { matcher1Guid, matcher2Guid },
63 | getResponses: null
64 | );
65 |
66 | var descriptions = new [] { new SpaceDescription()
67 | {
68 | name = FakeDigitalTwinsHttpClient.Space.Name,
69 | matchers = new [] {
70 | new MatcherDescription()
71 | {
72 | name = "Matcher1",
73 | dataTypeValue = "DataType1",
74 | },
75 | new MatcherDescription()
76 | {
77 | name = "Matcher2",
78 | dataTypeValue = "DataType2",
79 | }},
80 | }};
81 |
82 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
83 | Assert.Equal(2, httpHandler.PostRequests["matchers"].Count);
84 | Assert.False(httpHandler.GetRequests.ContainsKey("matchers"));
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/provisionSampleResourcesTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class ProvisionSampleResourcesTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid resource1Guid = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Models.Resource resource1 = new Models.Resource()
26 | {
27 | Id = resource1Guid.ToString(),
28 | Status = "Something",
29 | };
30 | private static HttpResponseMessage resource1GetResponse = new HttpResponseMessage()
31 | {
32 | StatusCode = HttpStatusCode.OK,
33 | Content = new StringContent(JsonConvert.SerializeObject(resource1)),
34 | };
35 |
36 | [Fact]
37 | public async Task GetProvisionSampleCreatesDescriptions()
38 | {
39 | var yaml = @"
40 | - name: Test1
41 | resources:
42 | - type: Type1
43 | - type: Type2
44 | ";
45 | var expectedDescriptions = new [] { new SpaceDescription()
46 | {
47 | name = "Test1",
48 | resources = new [] {
49 | new ResourceDescription()
50 | {
51 | type = "Type1",
52 | },
53 | new ResourceDescription()
54 | {
55 | type = "Type2",
56 | },
57 | },
58 | }};
59 | var actualDescriptions = await Actions.GetProvisionSampleTopology(new StringReader(yaml));
60 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
61 | }
62 |
63 | [Fact]
64 | public async Task CreateSingleResource()
65 | {
66 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
67 | postResponseGuids: new [] { resource1Guid },
68 | getResponses: new [] { resource1GetResponse });
69 |
70 | var descriptions = new [] { new SpaceDescription()
71 | {
72 | name = FakeDigitalTwinsHttpClient.Space.Name,
73 | resources = new [] { new ResourceDescription()
74 | {
75 | type = "ResourceType",
76 | }},
77 | }};
78 |
79 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
80 | Assert.Equal(1, httpHandler.PostRequests["resources"].Count);
81 | Assert.Equal(1, httpHandler.GetRequests["resources"].Count);
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/provisionSampleRoleAssignmentsTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class ProvisionSampleRoleAssignmentsTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid roleAssignmentGuid1 = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid roleAssignmentGuid2 = new Guid("00000000-0000-0000-0000-000000000002");
26 | private static Guid roleIdGuid = new Guid("99999999-0000-0000-0000-000000000000");
27 | private static Models.Space space1 = new Models.Space()
28 | {
29 | Id = new Guid("80000000-0000-0000-0000-000000000001").ToString(),
30 | Name = "Space 1",
31 | SpacePaths = new []
32 | {
33 | "Some1\\Path1",
34 | },
35 | };
36 | private static Models.UserDefinedFunction function1 = new Models.UserDefinedFunction()
37 | {
38 | Id = new Guid("90000000-0000-0000-0000-000000000001").ToString(),
39 | Name = "Function 1",
40 | };
41 | private static Models.UserDefinedFunction function2 = new Models.UserDefinedFunction()
42 | {
43 | Id = new Guid("90000000-0000-0000-0000-000000000002").ToString(),
44 | Name = "Function 2",
45 | };
46 | private static HttpResponseMessage function1GetResponse = new HttpResponseMessage()
47 | {
48 | StatusCode = HttpStatusCode.OK,
49 | Content = new StringContent(JsonConvert.SerializeObject(new [] { function1 })),
50 | };
51 | private static HttpResponseMessage function2GetResponse = new HttpResponseMessage()
52 | {
53 | StatusCode = HttpStatusCode.OK,
54 | Content = new StringContent(JsonConvert.SerializeObject(new [] { function2 })),
55 | };
56 | private static HttpResponseMessage space1GetResponse = new HttpResponseMessage()
57 | {
58 | StatusCode = HttpStatusCode.OK,
59 | Content = new StringContent(JsonConvert.SerializeObject(space1)),
60 | };
61 |
62 | [Fact]
63 | public async Task GetProvisionSampleCreatesDescriptions()
64 | {
65 | var yaml = @"
66 | - name: Test1
67 | roleassignments:
68 | - roleId: Id1
69 | objectName: Name1
70 | objectIdType: Type1
71 | - roleId: Id2
72 | objectName: Name2
73 | objectIdType: Type2
74 | ";
75 | var expectedDescriptions = new [] { new SpaceDescription()
76 | {
77 | name = "Test1",
78 | roleassignments = new [] {
79 | new RoleAssignmentDescription()
80 | {
81 | roleId = "Id1",
82 | objectName = "Name1",
83 | objectIdType = "Type1",
84 | },
85 | new RoleAssignmentDescription()
86 | {
87 | roleId = "Id2",
88 | objectName = "Name2",
89 | objectIdType = "Type2",
90 | },
91 | },
92 | }};
93 | var actualDescriptions = await Actions.GetProvisionSampleTopology(new StringReader(yaml));
94 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
95 | }
96 |
97 | [Fact]
98 | public async Task CreateRoleAssignmentWithUnknownTypeAndNameFails()
99 | {
100 | // How 'objectName' is used is based on objectIdType. In this test we choose an unknow type
101 | // and expect it then to fail the role assignment creation since it doesn't know how to use the name
102 |
103 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
104 | postResponseGuids: null,
105 | getResponses: new [] { space1GetResponse }
106 | );
107 |
108 | var descriptions = new [] { new SpaceDescription()
109 | {
110 | name = FakeDigitalTwinsHttpClient.Space.Name,
111 | roleassignments = new [] {
112 | new RoleAssignmentDescription()
113 | {
114 | roleId = roleIdGuid.ToString(),
115 | objectName = "SomeName",
116 | objectIdType = "UnknownObjectIdType"
117 | }},
118 | }};
119 |
120 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
121 | Assert.False(httpHandler.PostRequests.ContainsKey("roleassignments"));
122 | }
123 |
124 | [Fact]
125 | public async Task CreateTwoUdfRoleAssignments()
126 | {
127 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
128 | postResponseGuids: new [] { roleAssignmentGuid1, roleAssignmentGuid2 },
129 | getResponses: new [] { space1GetResponse, function1GetResponse, function2GetResponse }
130 | );
131 |
132 | var descriptions = new [] { new SpaceDescription()
133 | {
134 | name = FakeDigitalTwinsHttpClient.Space.Name,
135 | roleassignments = new [] {
136 | new RoleAssignmentDescription()
137 | {
138 | roleId = roleIdGuid.ToString(),
139 | objectName = "Function 1",
140 | objectIdType = "UserDefinedFunctionId"
141 | },
142 | new RoleAssignmentDescription()
143 | {
144 | roleId = roleIdGuid.ToString(),
145 | objectName = "Function 2",
146 | objectIdType = "UserDefinedFunctionId"
147 | }},
148 | }};
149 |
150 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
151 | Assert.Equal(2, httpHandler.PostRequests["roleassignments"].Count);
152 | Assert.Equal(2, httpHandler.GetRequests["userdefinedfunctions"].Count);
153 | Assert.False(httpHandler.GetRequests.ContainsKey("roleassignments"));
154 | }
155 | }
156 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/provisionSampleSensorsTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class ProvisionSampleSensorsTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid sensor1Guid = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid sensor2Guid = new Guid("00000000-0000-0000-0000-000000000002");
26 |
27 | [Fact]
28 | public async Task GetProvisionSampleCreatesDescriptions()
29 | {
30 | var yaml = @"
31 | - name: Test1
32 | devices:
33 | - name: Device1
34 | hardwareId: DeviceHardwareId1
35 | sensors:
36 | - dataType: SensorType1
37 | hardwareId: SensorHardwareId1
38 | ";
39 | var expectedDescriptions = new [] { new SpaceDescription()
40 | {
41 | name = "Test1",
42 | devices = new [] {
43 | new DeviceDescription()
44 | {
45 | name = "Device1",
46 | hardwareId = "DeviceHardwareId1",
47 | sensors = new [] {
48 | new SensorDescription()
49 | {
50 | dataType = "SensorType1",
51 | hardwareId = "SensorHardwareId1",
52 | }
53 | },
54 | },
55 | },
56 | }};
57 | var actualDescriptions = await Actions.GetProvisionSampleTopology(new StringReader(yaml));
58 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
59 | }
60 |
61 | [Fact]
62 | public async Task CreateTwoSensors()
63 | {
64 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithDevice(
65 | postResponseGuids: new [] { sensor1Guid, sensor2Guid },
66 | getResponses: Enumerable.Repeat(Responses.NotFound, 2)
67 | );
68 |
69 | var descriptions = new [] { new SpaceDescription()
70 | {
71 | name = FakeDigitalTwinsHttpClient.Space.Name,
72 | devices = new [] {
73 | new DeviceDescription()
74 | {
75 | name = FakeDigitalTwinsHttpClient.Device.Name,
76 | hardwareId = FakeDigitalTwinsHttpClient.Device.HardwareId,
77 | sensors = new [] {
78 | new SensorDescription()
79 | {
80 | dataType = "SensorType1",
81 | hardwareId = "SensorHardwareId1",
82 | },
83 | new SensorDescription()
84 | {
85 | dataType = "SensorType2",
86 | hardwareId = "SensorHardwareId2",
87 | }
88 | }
89 | }
90 | },
91 | }};
92 |
93 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
94 | Assert.Equal(2, httpHandler.PostRequests["sensors"].Count);
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/provisionSampleSpacesTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class ProvisionSampleSpacesTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid guid1 = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid guid2 = new Guid("00000000-0000-0000-0000-000000000002");
26 | private static Guid guid3 = new Guid("00000000-0000-0000-0000-000000000003");
27 | private static Models.Space space1 = new Models.Space()
28 | {
29 | Name = "Space1",
30 | Id = guid1.ToString(),
31 | Type = "Space1Type",
32 | };
33 |
34 | [Fact]
35 | public async Task GetProvisionSampleCreatesDescriptions()
36 | {
37 | var yaml = @"
38 | - name: Test1
39 | type: TestType1
40 | spaces:
41 | - name: Child1
42 | - name: Child2
43 | ";
44 | var expectedDescriptions = new [] { new SpaceDescription()
45 | {
46 | name = "Test1",
47 | type = "TestType1",
48 | spaces = new [] {
49 | new SpaceDescription()
50 | {
51 | name = "Child1",
52 | },
53 | new SpaceDescription()
54 | {
55 | name = "Child2",
56 | }},
57 | }};
58 | var actualDescriptions = await Actions.GetProvisionSampleTopology(new StringReader(yaml));
59 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
60 | }
61 |
62 | [Fact]
63 | public async Task CreateSpacesWithNoDescriptionsReturnsEmptyAndMakesNoRequests()
64 | {
65 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.Create();
66 |
67 | var results = await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, Array.Empty(), Guid.Empty);
68 |
69 | Assert.Equal(0, results.Count());
70 | Assert.False(httpHandler.PostRequests.ContainsKey("spaces"));
71 | Assert.False(httpHandler.GetRequests.ContainsKey("spaces"));
72 | }
73 |
74 | [Fact]
75 | public async Task CreateSpacesWithSingleSpaceMakesRequestsAndReturnsRootId()
76 | {
77 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.Create(
78 | postResponseGuids: new [] { guid1 },
79 | getResponses: Enumerable.Repeat(Responses.NotFound, 1000));
80 |
81 | var descriptions = new [] { new SpaceDescription()
82 | {
83 | name = "Test1",
84 | }};
85 |
86 | var results = await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
87 | Assert.Equal(guid1, results.Single().Id);
88 | Assert.Equal(1, httpHandler.PostRequests["spaces"].Count);
89 | Assert.Equal(1, httpHandler.GetRequests["spaces"].Count);
90 | }
91 |
92 | [Fact]
93 | public async Task CreateSpacesWithAlreadyCreatedSpaceUsesIt()
94 | {
95 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
96 | postResponseGuids: new [] { guid1 },
97 | space: space1);
98 |
99 | var descriptions = new [] { new SpaceDescription()
100 | {
101 | name = space1.Name,
102 | }};
103 |
104 | var results = await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
105 | Assert.Equal(guid1, results.Single().Id);
106 | Assert.False(httpHandler.PostRequests.ContainsKey("spaces"));
107 | Assert.Equal(1, httpHandler.GetRequests["spaces"].Count);
108 | }
109 |
110 | [Fact]
111 | public async Task CreateSpacesWithSingleRootAndChildrenMakesRequestsAndReturnsRootId()
112 | {
113 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.Create(
114 | postResponseGuids: new [] { guid1, guid2, guid3 },
115 | getResponses: Enumerable.Repeat(Responses.NotFound, 1000));
116 |
117 | var descriptions = new [] { new SpaceDescription()
118 | {
119 | name = "Test1",
120 | spaces = new [] {
121 | new SpaceDescription()
122 | {
123 | name = "Child1",
124 | },
125 | new SpaceDescription()
126 | {
127 | name = "Child2",
128 | }},
129 | }};
130 |
131 | var results = await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
132 | Assert.Equal(guid1, results.Single().Id);
133 | Assert.Equal(3, httpHandler.PostRequests["spaces"].Count);
134 | Assert.Equal(3, httpHandler.GetRequests["spaces"].Count);
135 | }
136 |
137 | [Fact]
138 | public async Task CreateSpacesWithMultipleRootsMakesRequestsAndReturnsAllRootIds()
139 | {
140 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.Create(
141 | postResponseGuids: new [] { guid1, guid2 },
142 | getResponses: Enumerable.Repeat(Responses.NotFound, 1000));
143 |
144 | var descriptions = new []
145 | {
146 | new SpaceDescription()
147 | {
148 | name = "Test1",
149 | },
150 | new SpaceDescription()
151 | {
152 | name = "Test2",
153 | }
154 | };
155 |
156 | var results = await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
157 | Assert.Equal(new [] { guid1, guid2 }, results.Select(r => r.Id));
158 | Assert.Equal(2, httpHandler.PostRequests["spaces"].Count);
159 | Assert.Equal(2, httpHandler.GetRequests["spaces"].Count);
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/provisionSampleUserDefinedFunctionsTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 | using Xunit;
7 | using Microsoft.Azure.DigitalTwins.Samples;
8 | using System.Net.Http;
9 | using System.Net;
10 | using System.Threading.Tasks;
11 | using Newtonsoft.Json;
12 | using System.Collections.Generic;
13 | using Moq;
14 | using System.IO;
15 | using System.Collections;
16 | using YamlDotNet.Serialization;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
20 | {
21 | public class ProvisionSampleUserDefinedFunctionsTests
22 | {
23 | private static Serializer yamlSerializer = new Serializer();
24 | private static Guid udfGuid1 = new Guid("00000000-0000-0000-0000-000000000001");
25 | private static Guid udfGuid2 = new Guid("00000000-0000-0000-0000-000000000002");
26 | private static Models.Matcher matcher1 = new Models.Matcher()
27 | {
28 | Id = new Guid("90000000-0000-0000-0000-000000000001").ToString(),
29 | Name = "Matcher1",
30 | SpaceId = null,
31 | };
32 | private static Models.Matcher matcher2 = new Models.Matcher()
33 | {
34 | Id = new Guid("90000000-0000-0000-0000-000000000002").ToString(),
35 | Name = "Matcher2",
36 | SpaceId = null,
37 | };
38 | private static HttpResponseMessage matcher1GetResponse = new HttpResponseMessage()
39 | {
40 | StatusCode = HttpStatusCode.OK,
41 | Content = new StringContent(JsonConvert.SerializeObject(new [] { matcher1 })),
42 | };
43 | private static HttpResponseMessage matchers1And2GetResponse = new HttpResponseMessage()
44 | {
45 | StatusCode = HttpStatusCode.OK,
46 | Content = new StringContent(JsonConvert.SerializeObject(new [] { matcher1, matcher2 })),
47 | };
48 | private static Models.UserDefinedFunction udf1 = new Models.UserDefinedFunction()
49 | {
50 | Id = udfGuid1.ToString(),
51 | Name = "User Defined Function 1",
52 | };
53 | private static HttpResponseMessage udf1GetResponse = new HttpResponseMessage()
54 | {
55 | StatusCode = HttpStatusCode.OK,
56 | Content = new StringContent(JsonConvert.SerializeObject(new [] { udf1 })),
57 | };
58 | [Fact]
59 | public async Task GetProvisionSampleCreatesDescriptions()
60 | {
61 | var yaml = @"
62 | - name: Test1
63 | userdefinedfunctions:
64 | - name: Function 1
65 | matcherNames:
66 | - matcher 1
67 | - matcher 2
68 | script: some1/path1
69 | - name: Function 2
70 | matcherNames:
71 | - matcher 3
72 | script: some2/path2
73 | ";
74 | var expectedDescriptions = new [] { new SpaceDescription()
75 | {
76 | name = "Test1",
77 | userdefinedfunctions = new [] {
78 | new UserDefinedFunctionDescription()
79 | {
80 | name = "Function 1",
81 | matcherNames = new [] { "matcher 1", "matcher 2" },
82 | script = "some1/path1",
83 | },
84 | new UserDefinedFunctionDescription()
85 | {
86 | name = "Function 2",
87 | matcherNames = new [] { "matcher 3" },
88 | script = "some2/path2",
89 | },
90 | },
91 | }};
92 | var actualDescriptions = await Actions.GetProvisionSampleTopology(new StringReader(yaml));
93 | Assert.Equal(yamlSerializer.Serialize(expectedDescriptions), yamlSerializer.Serialize(actualDescriptions));
94 | }
95 |
96 | [Fact]
97 | public async Task CreateTwoUserDefinedFunctions()
98 | {
99 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
100 | postResponseGuids: new [] { udfGuid1, udfGuid2 },
101 | getResponses: new [] { matchers1And2GetResponse, Responses.NotFound, matcher1GetResponse, Responses.NotFound }
102 | );
103 |
104 | var descriptions = new [] { new SpaceDescription()
105 | {
106 | name = FakeDigitalTwinsHttpClient.Space.Name,
107 | userdefinedfunctions = new [] {
108 | new UserDefinedFunctionDescription()
109 | {
110 | name = "Function 1",
111 | matcherNames = new [] { "Matcher1", "Matcher2" },
112 | script = "userDefinedFunctions/function1.js",
113 | },
114 | new UserDefinedFunctionDescription()
115 | {
116 | name = "Function 2",
117 | matcherNames = new [] { "Matcher1" },
118 | script = "userDefinedFunctions/function2.js",
119 | }},
120 | }};
121 |
122 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
123 | Assert.Equal(2, httpHandler.PostRequests["userdefinedfunctions"].Count);
124 | Assert.Equal(2, httpHandler.GetRequests["matchers"].Count);
125 | Assert.Equal(
126 | "http://bing.com/matchers?names=Matcher1,Matcher2&spaceIds=90000000-0000-0000-0000-000000000001",
127 | httpHandler.GetRequests["matchers"][0].RequestUri.ToString());
128 | Assert.Equal(
129 | "http://bing.com/matchers?names=Matcher1&spaceIds=90000000-0000-0000-0000-000000000001",
130 | httpHandler.GetRequests["matchers"][1].RequestUri.ToString());
131 | Assert.Equal(2, httpHandler.GetRequests["userdefinedfunctions"].Count);
132 | }
133 |
134 | [Fact]
135 | public async Task UpdateUserDefinedFunction()
136 | {
137 | (var httpClient, var httpHandler) = FakeDigitalTwinsHttpClient.CreateWithSpace(
138 | postResponseGuids: new [] { udfGuid1, udfGuid2 },
139 | getResponses: new [] { matcher1GetResponse, udf1GetResponse },
140 | patchResponses: new [] { Responses.OK }
141 | );
142 |
143 | var descriptions = new [] { new SpaceDescription()
144 | {
145 | name = FakeDigitalTwinsHttpClient.Space.Name,
146 | userdefinedfunctions = new [] {
147 | new UserDefinedFunctionDescription()
148 | {
149 | name = "Function 1",
150 | matcherNames = new [] { "Matcher1" },
151 | script = "userDefinedFunctions/function1.js",
152 | }},
153 | }};
154 |
155 | await Actions.CreateSpaces(httpClient, Loggers.SilentLogger, descriptions, Guid.Empty);
156 | Assert.False(httpHandler.PostRequests.ContainsKey("userdefinedfunctions"));
157 | Assert.Equal(1, httpHandler.PatchRequests["userdefinedfunctions"].Count);
158 | Assert.Equal(1, httpHandler.GetRequests["matchers"].Count);
159 | Assert.Equal(1, httpHandler.GetRequests["userdefinedfunctions"].Count);
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/responses.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System.Net;
5 | using System.Net.Http;
6 |
7 | namespace Microsoft.Azure.DigitalTwins.Samples.Tests
8 | {
9 | public static class Responses
10 | {
11 | public static HttpResponseMessage NotFound = new HttpResponseMessage()
12 | {
13 | StatusCode = HttpStatusCode.NotFound,
14 | };
15 |
16 | public static HttpResponseMessage OK = new HttpResponseMessage()
17 | {
18 | StatusCode = HttpStatusCode.OK,
19 | };
20 | }
21 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/userDefinedFunctions/function1.js:
--------------------------------------------------------------------------------
1 | function process(sensor, sensorReading) {
2 | log(`Processing function for ${sensor} with reading: ${sensorReading}.`);
3 | }
--------------------------------------------------------------------------------
/occupancy-quickstart/tests/userDefinedFunctions/function2.js:
--------------------------------------------------------------------------------
1 | function process(sensor, sensorReading) {
2 | log(`Processing function for ${sensor} with reading: ${sensorReading}.`);
3 | }
--------------------------------------------------------------------------------