Skip to content

Commit

Permalink
doc
Browse files Browse the repository at this point in the history
  • Loading branch information
toniarnold committed Feb 5, 2025
1 parent bae0d58 commit 3e96e3f
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 31 deletions.
36 changes: 24 additions & 12 deletions doc/docs/iot.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ is not available on this device".
## The Iot sample node

The `Iot` sample host can be published from VS with the RuntimeIdentifier
`linux-arm64`[^rpi3]. The included `limux-arm64.pubxml` in its
`.\Properties\PublishProfiles` points to an SMB share `Z:` connected via USB to
the target Raspberry PI. After deployment, it can be started with `./Iot`.
`linux-arm64`[^rpi3]. The included
[limux-arm64.pubxml](https://github.com/toniarnold/DMediatR/blob/main/src/Iot/Properties/PublishProfiles/limux-arm64.pubxml)
points to an SMB share `Z:` connected via USB to the target Raspberry PI. After
deployment, it can be started there locally with `./Iot`.

### The developer certificate

Expand All @@ -25,24 +26,33 @@ the target Raspberry PI. After deployment, it can be started with `./Iot`.
### The smoke test

The `appsettings.Iot.json` in `DMediatR.Tests` points to the Raspberry PI host
named `rpi`. It is written with a trailing dot to force Windows to actually
query the DNS server (a Pi-hole with its `/etc/hosts`).
The
[appsettings.Iot.json](https://github.com/toniarnold/DMediatR/blob/main/test/DMediatR.Tests/appsettings.Iot.json)
in `DMediatR.Tests` points to the Raspberry PI host named `rpi`. It is written
with a trailing dot to force Windows to actually query the DNS server (a
Pi-hole[^pihole] with its `/etc/hosts`):

[!code-javascript[IotTest.cs](../../test/DMediatR.Tests/appsettings.Iot.json?name=remotes)]

The certificate path in the `DMediatR:Certificate:FilePath` points to the `cert`
directory in the IoT application. If you create the initial certificate chain on
the Raspberry Pi with `.\Iot init`, manually copy it over to that directory.

The first of the two test methods in `IotTest.cs` simply sends a GET request
like a web browser would and asserts having received the same response:
The first of the two test methods in
[IotTest.cs](https://github.com/toniarnold/DMediatR/blob/main/test/DMediatR.Tests/Grpc/IotTest.cs)
simply sends a GET request like a web browser would and asserts having received
the same response:

[!code-csharp[IotTest.cs](../../test/DMediatR.Tests/Grpc/IotTest.cs?name=cputemp)]

The second one actually uses the DMediatR infrastructure and sends a serialized
MediatR `IRequest` to query the CPU temperature of the Raspberry PI using the
`Iot.Device.Bindings` NuGet package[^bindings]. Then, for plausibility, this
temperature can manually be compared to what e.g. the PI-hole admin page
reports:
MediatR `IRequest` to remotely query the CPU temperature of the Raspberry PI
using the `Iot.Device.Bindings` NuGet package[^bindings]. On the PI, the request
is handled locally in the
[TempHandler.cs](https://github.com/toniarnold/DMediatR/blob/main/src/Iot/TempHandler.cs)
and the result is sent back. Then, for plausibility, this temperature can
manually be compared to what e.g. the PI-hole [admin
page](http://rpi.:8081/admin/index.php) reports:

```text
CpuTemp of https://rpi.:18001/ is 46.7 ℃.
Expand All @@ -57,4 +67,6 @@ reports:

[^selfcontained]: [Deploying a self-contained app](https://learn.microsoft.com/en-us/dotnet/iot/deployment#deploying-a-self-contained-app)

[^pihole]: [https://pi-hole.net](https://pi-hole.net)

[^bindings]: [Iot.Device.Bindings](https://www.nuget.org/packages/Iot.Device.Bindings/)
44 changes: 31 additions & 13 deletions docs/docs/iot.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ <h1 id="iot">IoT</h1>
is not available on this device&quot;.</p>
<h2 id="the-iot-sample-node">The Iot sample node</h2>
<p>The <code>Iot</code> sample host can be published from VS with the RuntimeIdentifier
<code>linux-arm64</code><a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>. The included <code>limux-arm64.pubxml</code> in its
<code>.\Properties\PublishProfiles</code> points to an SMB share <code>Z:</code> connected via USB to
the target Raspberry PI. After deployment, it can be started with <code>./Iot</code>.</p>
<code>linux-arm64</code><a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>. The included
<a href="https://github.com/toniarnold/DMediatR/blob/main/src/Iot/Properties/PublishProfiles/limux-arm64.pubxml">limux-arm64.pubxml</a>
points to an SMB share <code>Z:</code> connected via USB to the target Raspberry PI. After
deployment, it can be started there locally with <code>./Iot</code>.</p>
<h3 id="the-developer-certificate">The developer certificate</h3>
<div class="IMPORTANT">
<h5>Important</h5>
Expand All @@ -110,14 +111,25 @@ <h5>Important</h5>
</blockquote>
</div>
<h3 id="the-smoke-test">The smoke test</h3>
<p>The <code>appsettings.Iot.json</code> in <code>DMediatR.Tests</code> points to the Raspberry PI host
named <code>rpi</code>. It is written with a trailing dot to force Windows to actually
query the DNS server (a Pi-hole with its <code>/etc/hosts</code>).</p>
<p>The
<a href="https://github.com/toniarnold/DMediatR/blob/main/test/DMediatR.Tests/appsettings.Iot.json">appsettings.Iot.json</a>
in <code>DMediatR.Tests</code> points to the Raspberry PI host named <code>rpi</code>. It is written
with a trailing dot to force Windows to actually query the DNS server (a
Pi-hole<a id="fnref:4" href="#fn:4" class="footnote-ref"><sup>4</sup></a> with its <code>/etc/hosts</code>):</p>
<pre><code class="lang-javascript" name="IotTest.cs">&quot;Remotes&quot;: {
&quot;CpuTemp&quot;: {
&quot;Host&quot;: &quot;rpi.&quot;,
&quot;Port&quot;: 18001,
&quot;OldPort&quot;: 18002
}
</code></pre>
<p>The certificate path in the <code>DMediatR:Certificate:FilePath</code> points to the <code>cert</code>
directory in the IoT application. If you create the initial certificate chain on
the Raspberry Pi with <code>.\Iot init</code>, manually copy it over to that directory.</p>
<p>The first of the two test methods in <code>IotTest.cs</code> simply sends a GET request
like a web browser would and asserts having received the same response:</p>
<p>The first of the two test methods in
<a href="https://github.com/toniarnold/DMediatR/blob/main/test/DMediatR.Tests/Grpc/IotTest.cs">IotTest.cs</a>
simply sends a GET request like a web browser would and asserts having received
the same response:</p>
<pre><code class="lang-csharp" name="IotTest.cs">[Test]
public async Task CpuTempReachable()
{
Expand All @@ -135,10 +147,13 @@ <h3 id="the-smoke-test">The smoke test</h3>

</code></pre>
<p>The second one actually uses the DMediatR infrastructure and sends a serialized
MediatR <code>IRequest</code> to query the CPU temperature of the Raspberry PI using the
<code>Iot.Device.Bindings</code> NuGet package<a id="fnref:4" href="#fn:4" class="footnote-ref"><sup>4</sup></a>. Then, for plausibility, this
temperature can manually be compared to what e.g. the PI-hole admin page
reports:</p>
MediatR <code>IRequest</code> to remotely query the CPU temperature of the Raspberry PI
using the <code>Iot.Device.Bindings</code> NuGet package<a id="fnref:5" href="#fn:5" class="footnote-ref"><sup>5</sup></a>. On the PI, the request
is handled locally in the
<a href="https://github.com/toniarnold/DMediatR/blob/main/src/Iot/TempHandler.cs">TempHandler.cs</a>
and the result is sent back. Then, for plausibility, this temperature can
manually be compared to what e.g. the PI-hole <a href="http://rpi.:8081/admin/index.php">admin
page</a> reports:</p>
<pre><code class="lang-text"> CpuTemp of https://rpi.:18001/ is 46.7 ℃.
</code></pre>
<div class="footnotes">
Expand All @@ -156,7 +171,10 @@ <h3 id="the-smoke-test">The smoke test</h3>
<p><a href="https://learn.microsoft.com/en-us/dotnet/iot/deployment#deploying-a-self-contained-app">Deploying a self-contained app</a><a href="#fnref:3" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:4">
<p><a href="https://www.nuget.org/packages/Iot.Device.Bindings/">Iot.Device.Bindings</a><a href="#fnref:4" class="footnote-back-ref">&#8617;</a></p>
<p><a href="https://pi-hole.net">https://pi-hole.net</a><a href="#fnref:4" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:5">
<p><a href="https://www.nuget.org/packages/Iot.Device.Bindings/">Iot.Device.Bindings</a><a href="#fnref:5" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
"docs/iot.html": {
"href": "docs/iot.html",
"title": "IoT | DMediatR",
"keywords": "IoT The IoT use case is an example of the way DMediatR is opinionated: Encourage the distribution of the same monolith across all nodes of a microservice architecture - it is the responsibility of the RPC caller to use only a node that actually provides the desired service. In this case, querying the CPU temperature on a Windows machine throws the exception \"Iot.Device.CpuTemperature is not available on this device\". The Iot sample node The Iot sample host can be published from VS with the RuntimeIdentifier linux-arm641. The included limux-arm64.pubxml in its .\\Properties\\PublishProfiles points to an SMB share Z: connected via USB to the target Raspberry PI. After deployment, it can be started with ./Iot. The developer certificate Important Although it is in fact not used at all, the dynamic late certificate assignment causes the server to require a developer certificate installed during startup. To avoid having to install the dotnet SDK2 despite publishing it as self-contained app3, you can copy a developer certificate manually to its directory created with mkdir -p ~/.dotnet/corefx/cryptography/x509stores/my The smoke test The appsettings.Iot.json in DMediatR.Tests points to the Raspberry PI host named rpi. It is written with a trailing dot to force Windows to actually query the DNS server (a Pi-hole with its /etc/hosts). The certificate path in the DMediatR:Certificate:FilePath points to the cert directory in the IoT application. If you create the initial certificate chain on the Raspberry Pi with .\\Iot init, manually copy it over to that directory. The first of the two test methods in IotTest.cs simply sends a GET request like a web browser would and asserts having received the same response: [Test] public async Task CpuTempReachable() { using var httpClient = await TestSetUp.GetHttpClientAsync(); var response = await httpClient.GetStringAsync(Remote.Remotes[\"CpuTemp\"].Address); Assert.That(response, Is.EqualTo(\"DMediatR gRPC endpoint\")); } [Test] public async Task GetRemoteTemp() { var temperature = await this.SendRemote(new TempRequest(), CancellationToken.None); TestContext.WriteLine($\"CpuTemp of {Remote.Remotes[\"CpuTemp\"].Address} is {temperature,0:f1} ℃.\"); } The second one actually uses the DMediatR infrastructure and sends a serialized MediatR IRequest to query the CPU temperature of the Raspberry PI using the Iot.Device.Bindings NuGet package4. Then, for plausibility, this temperature can manually be compared to what e.g. the PI-hole admin page reports: CpuTemp of https://rpi.:18001/ is 46.7 ℃. It's been a long way from the days when the special Windows distribution designed to run .NET on a Raspberry PI 3 refused to boot on a 3+ because of the \"+\"…↩ ./dotnet-install.sh --channel 8.0 with a scripted install↩ Deploying a self-contained app↩ Iot.Device.Bindings↩"
"keywords": "IoT The IoT use case is an example of the way DMediatR is opinionated: Encourage the distribution of the same monolith across all nodes of a microservice architecture - it is the responsibility of the RPC caller to use only a node that actually provides the desired service. In this case, querying the CPU temperature on a Windows machine throws the exception \"Iot.Device.CpuTemperature is not available on this device\". The Iot sample node The Iot sample host can be published from VS with the RuntimeIdentifier linux-arm641. The included limux-arm64.pubxml points to an SMB share Z: connected via USB to the target Raspberry PI. After deployment, it can be started there locally with ./Iot. The developer certificate Important Although it is in fact not used at all, the dynamic late certificate assignment causes the server to require a developer certificate installed during startup. To avoid having to install the dotnet SDK2 despite publishing it as self-contained app3, you can copy a developer certificate manually to its directory created with mkdir -p ~/.dotnet/corefx/cryptography/x509stores/my The smoke test The appsettings.Iot.json in DMediatR.Tests points to the Raspberry PI host named rpi. It is written with a trailing dot to force Windows to actually query the DNS server (a Pi-hole4 with its /etc/hosts): \"Remotes\": { \"CpuTemp\": { \"Host\": \"rpi.\", \"Port\": 18001, \"OldPort\": 18002 } The certificate path in the DMediatR:Certificate:FilePath points to the cert directory in the IoT application. If you create the initial certificate chain on the Raspberry Pi with .\\Iot init, manually copy it over to that directory. The first of the two test methods in IotTest.cs simply sends a GET request like a web browser would and asserts having received the same response: [Test] public async Task CpuTempReachable() { using var httpClient = await TestSetUp.GetHttpClientAsync(); var response = await httpClient.GetStringAsync(Remote.Remotes[\"CpuTemp\"].Address); Assert.That(response, Is.EqualTo(\"DMediatR gRPC endpoint\")); } [Test] public async Task GetRemoteTemp() { var temperature = await this.SendRemote(new TempRequest(), CancellationToken.None); TestContext.WriteLine($\"CpuTemp of {Remote.Remotes[\"CpuTemp\"].Address} is {temperature,0:f1} ℃.\"); } The second one actually uses the DMediatR infrastructure and sends a serialized MediatR IRequest to remotely query the CPU temperature of the Raspberry PI using the Iot.Device.Bindings NuGet package5. On the PI, the request is handled locally in the TempHandler.cs and the result is sent back. Then, for plausibility, this temperature can manually be compared to what e.g. the PI-hole admin page reports: CpuTemp of https://rpi.:18001/ is 46.7 ℃. It's been a long way from the days when the special Windows distribution designed to run .NET on a Raspberry PI 3 refused to boot on a 3+ because of the \"+\"…↩ ./dotnet-install.sh --channel 8.0 with a scripted install↩ Deploying a self-contained app↩ https://pi-hole.net↩ Iot.Device.Bindings↩"
},
"docs/misc.html": {
"href": "docs/misc.html",
Expand Down
2 changes: 1 addition & 1 deletion src/DMediatR/GrpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private static bool IsClientCertificateValidOld(X509Certificate2 cert, X509Chain

private static bool IsClientCertificateValid(bool old, X509Certificate2 cert, X509Chain? chain, SslPolicyErrors sslErrors)
{
// Ignore the too restricted default (sslErrors != SslPolicyErrors.None)
// A linux-arm64 server rejects if (sslErrors != SslPolicyErrors.None)
bool valid = false;
var policy = new X509ChainPolicy
{
Expand Down
2 changes: 1 addition & 1 deletion src/DMediatR/RemoteExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ private static bool IsServerCertificateValidOld(HttpRequestMessage requestMessag

private static bool IsServerCertificateValid(bool old, HttpRequestMessage requestMessage, X509Certificate2? cert, X509Chain? chain, SslPolicyErrors sslErrors)
{
// Connecting to a linux-arm64 server fails here: if (sslErrors != SslPolicyErrors.None)
// Connecting to a linux-arm64 server fails if (sslErrors != SslPolicyErrors.None)
bool valid = false;
var policy = new X509ChainPolicy
{
Expand Down
2 changes: 1 addition & 1 deletion src/DMediatR/TestSetUp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public async static Task<HttpClient> GetHttpClientAsync()
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
// for linux-arm64 server:
// Required for linux-arm64 server:
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12,
};
Expand Down
2 changes: 1 addition & 1 deletion src/Iot/TempHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Iot
{
[Local("TempHandler")]
[Local("CpuTemp")]
public class TempHandler : IRequestHandler<TempRequest, double>
{
public virtual async Task<double> Handle(TempRequest request, CancellationToken cancellationToken)
Expand Down
3 changes: 2 additions & 1 deletion test/DMediatR.Tests/appsettings.Iot.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Use persistent keys without the .Test suffix
{
"DMediatR": {
"Certificate": {
Expand All @@ -9,12 +8,14 @@
"ValidDays": "2",
"RenewBeforeExpirationDays": "1"
},
// <remotes>
"Remotes": {
"CpuTemp": {
"Host": "rpi.",
"Port": 18001,
"OldPort": 18002
}
// </remotes>
},
"Grpc": {
"MessagePackCompression": "Lz4BlockArray"
Expand Down

0 comments on commit 3e96e3f

Please sign in to comment.