diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 29b1fd4..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 3ac0c6f..cb0ff81 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ bld/ # Visual Studio 2015/2017 cache/options directory .vs/ +.vscode/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ @@ -328,5 +329,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ - -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index d1e7f4b..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/test/Mozilla.IoT.WebThing.Test/bin/Debug/netcoreapp2.1/Mozilla.IoT.WebThing.Test.dll", - "args": [], - "cwd": "${workspaceFolder}/test/Mozilla.IoT.WebThing.Test", - // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 7d3a389..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/test/Mozilla.IoT.WebThing.Test/Mozilla.IoT.WebThing.Test.csproj" - ], - "problemMatcher": "$tsc" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/test/Mozilla.IoT.WebThing.Test/Mozilla.IoT.WebThing.Test.csproj" - ], - "problemMatcher": "$tsc" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/test/Mozilla.IoT.WebThing.Test/Mozilla.IoT.WebThing.Test.csproj" - ], - "problemMatcher": "$tsc" - } - ] -} \ No newline at end of file diff --git a/Mozzila.IoT.WebThing.sln b/Mozzila.IoT.WebThing.sln index 8ccfb49..6b7992a 100644 --- a/Mozzila.IoT.WebThing.sln +++ b/Mozzila.IoT.WebThing.sln @@ -14,16 +14,18 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{370B1F76-EFE0-44D4-A395-59F5EF266112}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Itens", "Solution Itens", "{E90FFA85-A210-450A-AA08-528D7F8962C2}" -ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - .editorconfig = .editorconfig - azure-pipelines.yml = azure-pipelines.yml -EndProjectSection + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + .editorconfig = .editorconfig + README.md = README.md + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleThing", "sample\SampleThing\SampleThing.csproj", "{6FB673AA-FD52-4509-97C8-28572549F609}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mozilla.IoT.WebThing.AcceptanceTest", "test\Mozilla.IoT.WebThing.AcceptanceTest\Mozilla.IoT.WebThing.AcceptanceTest.csproj", "{0D709627-98FA-4A39-8631-90C982ADED44}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiThing", "sample\MultiThing\MultiThing.csproj", "{3CDFC9FB-F240-419A-800D-79C506CBDAE2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -85,11 +87,24 @@ Global {0D709627-98FA-4A39-8631-90C982ADED44}.Release|x64.Build.0 = Release|Any CPU {0D709627-98FA-4A39-8631-90C982ADED44}.Release|x86.ActiveCfg = Release|Any CPU {0D709627-98FA-4A39-8631-90C982ADED44}.Release|x86.Build.0 = Release|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|x64.ActiveCfg = Debug|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|x64.Build.0 = Debug|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|x86.ActiveCfg = Debug|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Debug|x86.Build.0 = Debug|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|Any CPU.Build.0 = Release|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|x64.ActiveCfg = Release|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|x64.Build.0 = Release|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|x86.ActiveCfg = Release|Any CPU + {3CDFC9FB-F240-419A-800D-79C506CBDAE2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {4999C9EF-BCC2-4252-A404-0161E655AAD9} = {F6436C38-AB0A-4D3F-8BA7-E2C0FA30D052} {63874B3F-6000-418D-BD19-C56A4D86B612} = {65C51E32-2901-4983-A238-0F931D9EB651} {6FB673AA-FD52-4509-97C8-28572549F609} = {370B1F76-EFE0-44D4-A395-59F5EF266112} {0D709627-98FA-4A39-8631-90C982ADED44} = {65C51E32-2901-4983-A238-0F931D9EB651} + {3CDFC9FB-F240-419A-800D-79C506CBDAE2} = {370B1F76-EFE0-44D4-A395-59F5EF266112} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index fcd24c9..d245bfd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # webthing +![Build](https://github.com/lillo42/webthing-csharp/workflows/Build/badge.svg) +[![NuGet](http://img.shields.io/nuget/v/Mozilla.IoT.WebThing.svg)](https://www.nuget.org/packages/Mozilla.IoT.WebThing/) + + + Implementation of an HTTP [Web Thing](https://iot.mozilla.org/wot/). # Using @@ -27,57 +32,51 @@ Imagine you have a dimmable light that you want to expose via the web of things First we create a new Thing: ```csharp -var light = new Thing +public class LampThing : Thing { - Name = "My Lamp", - Type = new [] { "OnOffSwitch", "Light" }, - Description = "A web connected lamp" -}; + public override string Name => "my-lamp-123"; + public override string? Title => "My Lamp"; + public override string? Description => "A web connected lamp"; + public override string[]? Type { get; } = new[] { "Light", "OnOffSwitch" }; + +} ``` Now we can add the required properties. -The **`on`** property reports and sets the on/off state of the light. For this, we need to have a `Property` object which holds the actual state and also a method to turn the light on/off. For our purposes, we just want to log the new state if the light is switched on/off. +The **`on`** property reports and sets the on/off state of the light. For this, we need to create a new property in Thing. For our purposes, we just want to log the new state if the light is switched on/off. ```csharp -var onDescription = new Dictionary +public class LampThing : Thing { - ["@type"] = "OnOffProperty", - ["title"] = "On/Off", - ["type"] = "boolean", - ["description"] = "Whether the lamp is turned on" -}; - -var property = new Property(light, "on", true, onDescription); -property.ValuedChanged += (sender, value) => -{ - Console.WriteLine($"On-State is now {value}"); -}; - -light.AddProperty(property); + ... + [ThingProperty(Type = new []{ "OnOffProperty" }, Title = "On/Off", + Description = "Whether the lamp is turned on")] + public bool On { get; set; } +} ``` The **`brightness`** property reports the brightness level of the light and sets the level. Like before, instead of actually setting the level of a light, we just log the level. ```csharp -var brightnessDescription = new Dictionary +public class LampThing : Thing { - ["@type"] = "BrightnessProperty", - ["title"] = "Brightness", - ["type"] = "integer", - ["description"] = "The level of light from 0-100", - ["minimum"] = 0, - ["maximum"] = 100, - ["unit"] = "percent" -}; - -var level = new Property(light, "level", true, onDescription); -level.ValuedChanged += (sender, value) => -{ - Console.WriteLine($"Brightness is now {value}"); -}; - -light.AddProperty(level); + ... + + private int _brightness; + [ThingProperty(Type = new []{ "BrightnessProperty" },Title = "Brightness", + Description = "The level of light from 0-100", Minimum = 0, Maximum = 100, + Unit = "percent")] + public int Brightness + { + get => _brightness; + set + { + _brightness = value; + Console.WriteLine($"Brightness is now {value}"); + } + } +} ``` Now we can add our newly created thing and add Thing middleware to Asp Net Core: @@ -86,13 +85,15 @@ Now we can add our newly created thing and add Thing middleware to Asp Net Core: // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddThing(option => option.IsSingleThing = true); + services.AddThings() + .AddThing(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - app.UseEndpoints(config => { - config.MapThing(light); + app.UseEndpoints(endpoints => + { + endpoints.MapThings(); }); } ``` @@ -108,33 +109,29 @@ A [`MultiLevelSensor`](https://iot.mozilla.org/schemas/#MultiLevelSensor) (a sen First we create a new Thing: ```csharp -var sensor = new Thing +public class Humidity : Thing { - Name = "My Humidity Sensor", - Type = new [] { "MultiLevelSensor" }, - Description = "A web connected humidity sensor" -}; + public override string? Title => "My Humidity Sensor"; + + public override string[]? Type { get; } = new[] {"MultiLevelSensor"}; + + public override string? Description => "A web connected humidity sensor"; +} ``` Then we create and add the appropriate property: * `level`: tells us what the sensor is actually reading * Contrary to the light, the value cannot be set via an API call, as it wouldn't make much sense, to SET what a sensor is reading. Therefore, we are creating a *readOnly* property. - ```csharp - var levelDescription = new Dictionary - { - ["@type"] = "LevelProperty", - ["title"] = "Humidity", - ["type"] = "number", - ["description"] = "The current humidity in %", - ["minimum"] = 0, - ["maximum"] = 100, - ["unit"] = "percent", - ["readOnly"] = true - }; - - sensor.AddProperty(new Property(sensor, "level", 0, levelDescription)); - ``` +```csharp +public class Humidity : Thing +{ + ... + [ThingProperty(Type = new []{"LevelProperty"}, Title = "Humidity", Description = "The current humidity in %", + Minimum = 0, Maximum = 100, Unit = "percent")] + public double Level { get; private set; } +} +``` Now we have a sensor that constantly reports 0%. To make it usable, we need a thread or some kind of inAdd when the sensor has a new reading available. For this purpose we start a task that queries the physical sensor every few seconds. For our purposes, it just calls a fake method. @@ -143,8 +140,10 @@ Now we have a sensor that constantly reports 0%. To make it usable, we need a th Task.Factory.StartNew(async () => { await Task.Delay(3_000); - await level.NotifyOfExternalUpdate(ReadFromGPIO()); + await Level = ReadFromGPIO(); }); ``` -This will update our `Value` object with the sensor readings via the `this.level.NotifyOfExternalUpdate(ReadFromGPIO());` call. The `Value` object now notifies the property and the thing that the value has changed, which in turn notifies all websocket listeners. +## Limitation + +Current version, 2.0.0-previewX, Websocket is not working \ No newline at end of file diff --git a/sample/.DS_Store b/sample/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/sample/.DS_Store and /dev/null differ diff --git a/sample/MultiThing/MultiThing.csproj b/sample/MultiThing/MultiThing.csproj new file mode 100644 index 0000000..ba34a27 --- /dev/null +++ b/sample/MultiThing/MultiThing.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.1 + + + + + + + + diff --git a/sample/MultiThing/Program.cs b/sample/MultiThing/Program.cs new file mode 100644 index 0000000..2c918f7 --- /dev/null +++ b/sample/MultiThing/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace MultiThing +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/sample/MultiThing/Startup.cs b/sample/MultiThing/Startup.cs new file mode 100644 index 0000000..bb44ca6 --- /dev/null +++ b/sample/MultiThing/Startup.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using MultiThing.Things; + +namespace MultiThing +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddThings() + .AddThing() + .AddThing(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapThings(); + }); + } + } +} diff --git a/sample/MultiThing/Things/ExampleDimmableLight.cs b/sample/MultiThing/Things/ExampleDimmableLight.cs new file mode 100644 index 0000000..f9e4549 --- /dev/null +++ b/sample/MultiThing/Things/ExampleDimmableLight.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Mozilla.IoT.WebThing; +using Mozilla.IoT.WebThing.Attributes; + +namespace MultiThing.Things +{ + public class ExampleDimmableLight : Thing + { + public override string Name => "my-lamp-1234"; + + public override string? Title => "My Lamp"; + + public override string[]? Type { get; } = new[] {"OnOffSwitch", "Light"}; + + public override string? Description => "A web connected lamp"; + + private bool _on = true; + + [ThingProperty(Title = "On/Off", Type = new []{ "OnOffProperty" }, Description = "Whether the lamp is turned on")] + public bool On + { + get => _on; + set + { + _on = value; + + Console.WriteLine($"On-State is now {_on}"); + } + } + + private int _brightness = 50; + + [ThingProperty(Type = new []{ "BrightnessProperty" }, Title = "Brightness", Description = "The level of light from 0-100", + Minimum = 0, Maximum = 100, Unit = "percent")] + public int Brightness + { + get => _brightness; + set + { + _brightness = value; + + Console.WriteLine($"Brightness is now {_brightness}"); + } + } + + + [ThingAction(Title = "Fade", Description = "Fade the lamp to a given level")] + public async Task Fade( + [ThingParameter(Minimum = 0, Maximum = 100, Unit = "percent")]int brightness, + [ThingParameter(Minimum = 1, Unit = "milliseconds")]int duration, + [FromServices]ILogger logger) + { + await Task.Delay(duration); + + logger.LogInformation("Going to set Brightness to {brightness}", brightness); + Brightness = brightness; + + logger.LogInformation("Going to send event OverheatedEvent"); + OverheatedEvent?.Invoke(this, 102); + } + + [ThingEvent(Description = "The lamp has exceeded its safe operating temperature", Unit = "degree celsius")] + public event EventHandler OverheatedEvent; + } +} diff --git a/sample/MultiThing/Things/FakeGpioHumiditySensor.cs b/sample/MultiThing/Things/FakeGpioHumiditySensor.cs new file mode 100644 index 0000000..646020a --- /dev/null +++ b/sample/MultiThing/Things/FakeGpioHumiditySensor.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using Mozilla.IoT.WebThing; +using Mozilla.IoT.WebThing.Attributes; + +namespace MultiThing.Things +{ + public class FakeGpioHumiditySensor : Thing + { + private readonly Random _random; + public FakeGpioHumiditySensor() + { + _random = new Random(); + Task.Factory.StartNew(() => + { + while (true) + { + Task.Delay(3_000).GetAwaiter().GetResult(); + var newLevel = ReadFromGPIO(); + Console.WriteLine("setting new humidity level: {0}", newLevel); + Level = newLevel; + } + }, TaskCreationOptions.LongRunning); + } + public override string Name => "my-humidity-sensor-1234"; + + public override string? Title => "My Humidity Sensor"; + + public override string[]? Type { get; } = new[] {"MultiLevelSensor"}; + + public override string? Description => "A web connected humidity sensor"; + + + [ThingProperty(Type = new []{"LevelProperty"}, Title = "Humidity", Description = "The current humidity in %", + Minimum = 0, Maximum = 100, Unit = "percent")] + public double Level { get; private set; } + + /// + /// Mimic an actual sensor updating its reading every couple seconds. + /// + /// + private double ReadFromGPIO() { + return Math.Abs(70.0d * _random.Next() * (-0.5 + _random.Next())); + } + + } +} diff --git a/sample/MultiThing/appsettings.Development.json b/sample/MultiThing/appsettings.Development.json new file mode 100644 index 0000000..dba68eb --- /dev/null +++ b/sample/MultiThing/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/sample/MultiThing/appsettings.json b/sample/MultiThing/appsettings.json new file mode 100644 index 0000000..81ff877 --- /dev/null +++ b/sample/MultiThing/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/sample/SampleThing/Things/LampThing.cs b/sample/SampleThing/Things/LampThing.cs index 6f9fe36..3ea6cb8 100644 --- a/sample/SampleThing/Things/LampThing.cs +++ b/sample/SampleThing/Things/LampThing.cs @@ -1,4 +1,7 @@ using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Mozilla.IoT.WebThing; using Mozilla.IoT.WebThing.Attributes; @@ -6,26 +9,7 @@ namespace SampleThing.Things { public class LampThing : Thing { - public LampThing() - { - // Task.Factory.StartNew(() => - // { - // while (true) - // { - // Task.Delay(3_000).GetAwaiter().GetResult(); - // var @event = Overheated; - // try - // { - // @event?.Invoke(this, 10); - // } - // catch (Exception e) - // { - // Console.WriteLine(e); - // } - // } - // }); - } - public override string Name => "Lamp"; + public override string Name => "my-lamp-123"; public override string? Title => "My Lamp"; public override string? Description => "A web connected lamp"; public override string[]? Type { get; } = new[] { "Light", "OnOffSwitch" }; @@ -37,7 +21,7 @@ public LampThing() Description = "The level of light from 0-100", Minimum = 0, Maximum = 100)] public int Brightness { get; set; } - [ThingEvent(Title = "Overheated", + [ThingEvent(Title = "Overheated", Unit = "degree celsius", Type = new [] {"OverheatedEvent"}, Description = "The lamp has exceeded its safe operating temperature")] public event EventHandler Overheated; @@ -45,11 +29,18 @@ public LampThing() [ThingAction(Name = "fade", Title = "Fade", Type = new []{"FadeAction"}, Description = "Fade the lamp to a given level")] - public void Fade( - [ThingParameter(Minimum = 0, Maximum = 100)]int level, - [ThingParameter(Minimum = 0, Unit = "milliseconds")]int duration) + public async Task Fade( + [ThingParameter(Minimum = 0, Maximum = 100, Unit = "percent")]int brightness, + [ThingParameter(Minimum = 1, Unit = "milliseconds")]int duration, + [FromServices]ILogger logger) { - Console.WriteLine("Fade executed...."); + await Task.Delay(duration); + + logger.LogInformation("Going to set Brightness to {brightness}", brightness); + Brightness = brightness; + + logger.LogInformation("Going to send event Overheated"); + Overheated?.Invoke(this, 102); } } } diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index e330289..0000000 Binary files a/src/.DS_Store and /dev/null differ diff --git a/src/Mozilla.IoT.WebThing/.DS_Store b/src/Mozilla.IoT.WebThing/.DS_Store deleted file mode 100644 index 2ba683d..0000000 Binary files a/src/Mozilla.IoT.WebThing/.DS_Store and /dev/null differ diff --git a/test/.DS_Store b/test/.DS_Store deleted file mode 100644 index bf29070..0000000 Binary files a/test/.DS_Store and /dev/null differ diff --git a/test/Mozilla.IoT.WebThing.Test/.DS_Store b/test/Mozilla.IoT.WebThing.Test/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/test/Mozilla.IoT.WebThing.Test/.DS_Store and /dev/null differ