TypeEdge/README.md

297 строки
12 KiB
Markdown
Исходник Обычный вид История

2018-05-25 23:25:31 +03:00
# Azure IoT TypeEdge
2018-05-26 20:50:04 +03:00
The **Azure IoT TypeEdge** introduces a strongly typed flavor of the inherently loosely coupled vanilla [Azure IoT Edge](https:/azure.microsoft.com/en-us/services/iot-edge/).
2018-05-25 23:25:31 +03:00
Specifically:
2018-05-26 20:50:04 +03:00
2018-05-25 23:25:31 +03:00
- It removes all configuration burden from an IoT Edge application, because configuration can be now automatically generated by code reflection.
- Introduces compile time types checking across all modules
- It adds the ability to **emulate an IoT Edge device on the development environment**, by running all components in memory, no containers involved
2018-05-31 19:03:42 +03:00
- It simplifies the IoT Edge development down to an single F5 experience
2018-05-25 23:25:31 +03:00
2018-05-31 19:03:42 +03:00
## Initial Setup
2018-05-25 23:25:31 +03:00
2018-05-31 19:03:42 +03:00
Make sure you have [Docker](https://docs.docker.com/engine/installation/), the latest [.NET Core SDK](https://github.com/dotnet/core/blob/master/release-notes/download-archives/2.1.0-download.md) (version 2.1.300) and your git credentials to login [here](https://msblox-03.visualstudio.com/csetypeedge)
2018-05-26 20:50:04 +03:00
2018-05-31 06:26:31 +03:00
2018-05-31 19:03:42 +03:00
> Note: To get your git credentials, navigate to the [VSTS repo](https://msblox-03.visualstudio.com/csetypeedge) and under **"clone to your computer"** section, click **"Generate Git Credentials"**.
2018-05-26 21:10:49 +03:00
2018-05-25 23:25:31 +03:00
## QuickStart
2018-05-31 19:03:42 +03:00
Here is the quickest way to get started with **TypeEdge**. In this quick start you will create an IoT Edge application with two modules and run it in the IoT Edge emulator:
2018-05-25 23:25:31 +03:00
2018-05-26 20:50:04 +03:00
1. Install the TypeEdge .NET Core solution template. Just type:
```
2018-05-31 06:26:31 +03:00
dotnet new -i TypeEdge.Application
2018-05-26 20:50:04 +03:00
```
2018-05-31 06:26:31 +03:00
>**Note:** If you already installed the template and you want to **upgrade to a newer template version**, you need to clear the dotnet http and template cache
2018-05-25 23:25:31 +03:00
```
2018-05-26 21:03:41 +03:00
dotnet nuget locals http-cache --clear
2018-05-26 20:50:04 +03:00
dotnet new --debug:reinit
2018-05-25 23:25:31 +03:00
```
2. Copy the **iothubowner connection string** from your development Azure **IoT Hub**.
> The **iothubowner** is required because TypeEdge needs to provision a new device with the generated deployment configuration.
1. Create a new IoT TypeEdge application:
> You can choose the TypeEdge application and modules names of this template. In the example bellow, the application is called **Thermostat**, and the two modules are called **SensorModule** and **PreprocessorModule**. These names will be used as class names, so **Pascal casing** is suggested.
```
dotnet new typeedgeapp -n Thermostat -m1 SensorModule -m2 PreprocessorModule -cs "YOUR_IOTHUBOWNER_CONNECTION"
```
2018-05-26 22:10:05 +03:00
2018-05-31 06:26:31 +03:00
1. Temporary step:
Navigate inside the root folder to add the private packages source. Then, **add your git credentials to the command bellow** and run it. This will download nuget.exe and add the private packages source in your solution.
2018-05-25 23:25:31 +03:00
```
2018-05-26 22:10:05 +03:00
cd Thermostat
2018-05-31 06:26:31 +03:00
addPrivateSource.bat USERNAME PASSWORD
```
3. Open in VS Code/Visual Studio 2017 and hit F5:
```
2018-05-26 22:10:05 +03:00
code .
2018-05-25 23:25:31 +03:00
```
2018-05-31 19:14:30 +03:00
..or run it in the command line:
2018-05-25 23:25:31 +03:00
```
2018-05-31 06:26:31 +03:00
dotnet build Thermostat.sln
cd Thermostat.Emulator
2018-05-25 23:25:31 +03:00
dotnet run
```
2018-05-26 22:10:05 +03:00
2018-05-31 19:14:30 +03:00
> Note: You should see now the Edge Hub starting up..
2018-05-25 23:25:31 +03:00
![](images/IoTEdge.png) .. and the messages flowing in .. ![](images/messages.png)
2018-05-31 19:14:30 +03:00
**Congratulations!**
You just created your first **TypeEdge** application. Continue reading bellow to learn how to deploy this application to an IoT Device, or take the time to understand [how it works](#how).
2018-05-31 06:26:31 +03:00
## Device Deployment
In the application root folder, type:
docker-compose build
This will build your docker images for the device deployment. Final step is to push the images to the docker registry.
2018-05-31 19:03:42 +03:00
>Note: the registry is configured in the .env file inside the root folder, with initial value, the localhost:5000. **Make sure you run the the emulator if you edit the .env file**, to update the cloud IoT Device deployment configuration.
2018-05-31 06:26:31 +03:00
docker-compose push
>Note: If your registry requires authentication, you need to provide the registry credentials to docker:
docker login YOUR_REGISTRY -u YOUR_USERNAME -p YOUR_PASSWORD
2018-05-31 19:03:42 +03:00
This was the final step. All IoT Edge containers are ready to be deployed to the device.
On the device host, **get the device connection string from Azure portal**, and run:
iotedgectl setup --connection-string "THE_DEVICE_CONNECTION_STRING" --auto-cert-gen-force-no-passwords
>Note: The device name is configured in the appsettings.json of the emulator project. **Make sure you run the emulator and rebuilt the containers if you change that name**. The emulator will provision a new device if the device does not exist.
if your registry requires authentication, you need to run
iotedgectl login --address YOUR_REGISTRY_ADDRESS --username YOUR_REGISTRY_USERNAME --password YOUR_REGISTRY_PASSWORD
Finally, start the runtime:
iotedgectl start
Read more [here](https://docs.microsoft.com/en-us/azure/iot-edge/quickstart#configure-the-iot-edge-runtime) about the IoT Edge device deployment.
2018-05-31 06:26:31 +03:00
## <a name="how">How it works</a>
2018-05-25 23:25:31 +03:00
**TypeEdge** for the moment only supports .NET Core C#. A **TypeEdge** application is a collection of **TypeEdge Modules**.
### Module interface
**TypeEdge** leverages **interfaces** to define the structure and behavior of the modules. A typical example of a **TypeEdge module definition** is:
2018-05-31 19:03:42 +03:00
2018-05-25 23:25:31 +03:00
```cs
2018-05-31 06:26:31 +03:00
[TypeModule]
2018-05-25 23:25:31 +03:00
public interface ISensorModule
{
Output<SensorModuleOutput> Output { get; set; }
ModuleTwin<SensorModuleTwin> Twin { get; set; }
bool ResetModule(int sensorThreshold);
}
```
2018-05-26 20:50:04 +03:00
This module has a strongly typed output called ***Output*** and the messages type is ***SensorModuleOutput***. Similarly, it has a module twin called ***Twin*** with type ***SensorModuleTwin***
2018-05-25 23:25:31 +03:00
> Note: **TypeEdge** allows you to define multiple twin properties to enable partial twin updates
Finally, this module has a method that can be invoked directly with the bellow method signature:
```cs
bool ResetModule(int sensorThreshold);
```
### Module implementation
After describing the module behavior and structure with an interface, the next step is to implement this interface. This is effectively the code that will run in the **TypeEdge** module. Below is an implementation example of the above interface:
2018-05-26 21:03:41 +03:00
<details>
2018-05-26 21:10:49 +03:00
<summary>Click to see the full <b>SensorModule</b> implementation code</summary>
2018-05-26 21:03:41 +03:00
2018-05-25 23:25:31 +03:00
```cs
public class SensorModule : EdgeModule, ISensorModule
{
public Output<SensorModuleOutput> Output { get; set; }
public ModuleTwin<SensorModuleTwin> Twin { get; set; }
public bool ResetModule(int sensorThreshold)
{
System.Console.WriteLine($"New sensor threshold:{sensorThreshold}");
return true;
}
public override async Task<ExecutionResult> RunAsync()
{
while (true)
{
await Output.PublishAsync(
new SensorModuleOutput() {
Data = new System.Random().NextDouble().ToString() });
System.Threading.Thread.Sleep(1000);
}
return await base.RunAsync();
}
}
```
2018-05-26 21:03:41 +03:00
</details>
2018-05-26 21:10:49 +03:00
<br>
2018-05-31 06:26:31 +03:00
A <b>TypeEdge</b> module can override any of the virtual methods of the base class ``EdgeModule``. As demonstrated in the above example, the ``RunAsync`` method is used for implementing long running loops, typically useful for modules that read sensor values. Another virtual method is ``Configure``, which can be used to read custom module configuration during startup. Finally, the ``BuildSubscriptions`` is used to define handlers to incoming messages.
2018-05-25 23:25:31 +03:00
The complete ``EdgeModule`` definition is:
```cs
public abstract class EdgeModule
{
public virtual CreationResult Configure(IConfigurationRoot configuration);
public virtual Task<ExecutionResult> RunAsync();
}
```
### Module Subscriptions
**TypeEdge** uses the pub/sub pattern for all module I/O, except for the direct methods. This means that a module can subscribe to other module outputs, and publish messages to their inputs. To do this, a reference to the module interface definition is required. **TypeEdge** uses dependency injection to determine the referenced modules.
Bellow, is the constructor of the second module included in the application template called ``PreprocessorModule``, that references the ``SensorModule`` via its interface.
```cs
public PreprocessorModule(ISensorModule proxy)
{
this.proxy = proxy;
}
```
Using this proxy, the ``PreprocessorModule`` module can interact with the ``SensorModule``:
```cs
Input.Subscribe(proxy.Output, async (msg) =>
{
await Output.PublishAsync(new PreprocessorModuleOutput()
{
Data = msg.Data,
Metadata = System.DateTime.UtcNow.ToShortTimeString()
});
return MessageResult.OK;
});
```
2018-05-31 06:26:31 +03:00
In this example, the ``PreprocessorModule's`` input called ``Input``, subscribes to ``SensorModule's`` output, called ``Output``, and defines a subscription handler, a delegate that will be called every time the ``SensorModule`` sends a messages through its ``Output ``. In this example, the ``PreprocessorModule`` will enrich the incoming message, and publish it to its output called ``Output ``. These subscriptions need to be declared in the default constructor.
2018-05-25 23:25:31 +03:00
The complete code of the template's ``PreprocessorModule`` is:
2018-05-26 21:03:41 +03:00
<details>
2018-05-26 21:10:49 +03:00
<summary>Click to see the full <b>PreprocessorModule</b> implementation code</summary>
2018-05-26 21:03:41 +03:00
2018-05-25 23:25:31 +03:00
```cs
public class PreprocessorModule : EdgeModule, IPreprocessorModule
{
public Output<PreprocessorModuleOutput> Output { get; set; }
public Input<SensorModuleOutput> Input { get; set; }
public ModuleTwin<PreprocessorModuleTwin> Twin { get; set; }
public PreprocessorModule(ISensorModule proxy)
{
Input.Subscribe(proxy.Output, async (msg) =>
{
await Output.PublishAsync(new PreprocessorModuleOutput()
{
Data = msg.Data,
Metadata = System.DateTime.UtcNow.ToShortTimeString()
});
return MessageResult.OK;
});
}
}
```
2018-05-26 21:03:41 +03:00
</details>
2018-05-25 23:25:31 +03:00
### Emulator
The emulator references the Runtime bits achieve the emulation. Under the hood, the emulator starts a console application that hosts the Edge Hub and all referenced modules. It will also provision a new Edge device to your designated IoT Hub (by the iothubowner connection string). This device will contain a fully functional deployment configuration, ready to be used to an actual device deployment.
To reference modules in an emulator application, both the interface and the implementation class of the module is required:
```cs
host.RegisterModule<ISensorModule, Modules.SensorModule>();
```
Finally, all subscriptions beyond to context of a single module can be defined here. For example:
```cs
host.Upstream.Subscribe(host.GetProxy<IPreprocessorModule>().Output);
```
Below is the complete template emulator code for reference.
2018-05-26 21:03:41 +03:00
<details>
2018-05-26 21:10:49 +03:00
<summary>Click to see the full <b>emulator</b> code</summary>
2018-05-26 21:03:41 +03:00
2018-05-25 23:25:31 +03:00
```cs
public static async Task Main(string[] args)
{
//TODO: Set your IoT Hub iothubowner connection string in appsettings.json
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
2018-05-31 06:26:31 +03:00
.AddDotenvFile()
2018-05-25 23:25:31 +03:00
.AddCommandLine(args)
.Build();
var host = new TypeEdgeHost(configuration);
//TODO: Register your TypeEdge Modules here
host.RegisterModule<ISensorModule, Modules.SensorModule>();
host.RegisterModule<IPreprocessorModule, Modules.PreprocessorModule>();
//TODO: Define all cross-module subscriptions
host.Upstream.Subscribe(host.GetProxy<IPreprocessorModule>().Output);
host.Build();
await host.RunAsync();
Console.WriteLine("Press <ENTER> to exit..");
Console.ReadLine();
}
```
2018-05-26 21:03:41 +03:00
</details>
2018-05-25 23:25:31 +03:00
### Proxy
**TypeEdge** adds functionality also for service application development (cloud side application). This template will create a Proxy project, useful for cloud side interaction with the TypeEdge application. It also leverages the interfaces as the way to create a strongly typed proxy client. The code to call a direct method of a TypeEdge module from the could side is literally one line:
```cs
ProxyFactory.GetModuleProxy<ISensorModule>().ResetModule(4);
```
### Solution structure
Apparently, to reference module definition interfaces and to avoid coupling the module implementation code together, these interfaces need to be defined in a separate project that will be commonly shared across the solution, containing only the definition interfaces and the referenced types.
![](images/solution.png)