I thought first my ASP.NET Core edition of Hello, Blinky will be my last Hello, Blinky for long time. But then something reminded me of Blazor and I thought why not build Blazor edition of Hello, Blinky for Windows IoT Core and Raspberry Pi? After some hacking I made it work. Here’s my Hello, Blinky for Blazor.
Who the hell is Hello, Blinky?
Hello, Blinky is kind of Hello, World from Raspberry Pi and other microboards world. It’s possible to start also there with Hello, World but why not to do something more interesting with connected electronics? This is what those boards are made for.
Hello, Blinky is about blinking LED lamp. It’s one of simplest things to do on boards like Raspberry Pi. There are many hands-on examples of Hello, Blinky in web for different languages, tools and utilities. This one here is for Blazor.
Wiring
To use LED lamp with Raspberry Pi we need to buy one, of course. We need also breadboard, wires and resistor. Just visit your local electronics shop and ask them for these components. They know well what to sell you.
Wiring is actually easy. Just connect LED, resistor and wires like shown on the following image.
NB! I’m using Raspberry Pi with Windows 10 IoT Core (there’s free version available). This code should actually work also on Linux but I have not tried it out and I have no idea if publishing works same way for Blazor applications we want to host on Linux.
With wiring done it’s time to start coding.
Blazor solution
I decided to go with client-side Blazor application that is supported by server-side web application.
Server-side Blazor handles events in server. I mean UI events are handled in server. In this case UI and server communicate using web sockets and SignalR.
This is something I want to avoid. Raspberry Pi is small board with not much resources. At least the one I have is not very powerful. I don’t want to put UI workload there as browser has way more resources to use.
With this in mind I created Visual Studio solution shown on image on right. BlazorHelloBlinky.Client is client-side Blazor application and BlazorHelloBlinky.Server is web application that runs on Raspberry Pi.
Blinking LED from ASP.NET Core controller
Before everything else we need a class to blink LED lamp. There’s no programming concept for blinking. No command like make-led-lamp-blink(). We have to write it by ourselves.
Here is what blinking cycle means for computer:
- Send signal to GPIO pin
- Wait for one second
- Cut signal off
- Wait for one one second
- Go to step 1
The problem is we cannot blink LED with just one command. We need something that is going through this cycle until it is stopped. For this I write LedBlinkClient class. It hosts task with endless loop but inside the loop it checks if it’s time to stop.
Here is LedBlinkClient class for server-side web application (it needs System.Device.Gpio Nuget package to work).
public class LedBlinkClient : IDisposable { private const int LedPin = 17; private const int LightTimeInMilliseconds = 1000; private const int DimTimeInMilliseconds = 200; private bool disposedValue = false; private object _locker = new object(); private bool _isBlinking = false; private Task _blinkTask; private CancellationTokenSource _tokenSource; private CancellationToken _token; public void StartBlinking() { if (_blinkTask != null) { return; } lock (_locker) { if (_blinkTask != null) { return; } _tokenSource = new CancellationTokenSource(); _token = _tokenSource.Token; _blinkTask = new Task(() => { using (var controller = new GpioController()) { controller.OpenPin(LedPin, PinMode.Output); _isBlinking = true; while (true) { if (_token.IsCancellationRequested) { break; } controller.Write(LedPin, PinValue.High); Thread.Sleep(LightTimeInMilliseconds); controller.Write(LedPin, PinValue.Low); Thread.Sleep(DimTimeInMilliseconds); } _isBlinking = false; } }); _blinkTask.Start(); } } public void StopBlinking() { if (_blinkTask == null) { return; } lock (_locker) { if (_blinkTask == null) { return; } _tokenSource.Cancel(); _blinkTask.Wait(); _isBlinking = false; _tokenSource.Dispose(); _blinkTask.Dispose(); _tokenSource = null; _blinkTask = null; } } public bool IsBlinking { get { return _isBlinking; } } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { StopBlinking(); } disposedValue = true; } } public void Dispose() { Dispose(true); } }
Server-side application needs also controller so Blazor can control blinking. Here is the simple controller I created.
[Route("api/[controller]")] public class BlinkyController { private readonly LedBlinkClient _blinkClient; public BlinkyController(LedBlinkClient blinkClient) { _blinkClient = blinkClient; } [HttpGet("[action]")] public bool IsBlinking() { return _blinkClient.IsBlinking; } [HttpGet("[action]")] public void StartBlinking() { _blinkClient.StartBlinking(); } [HttpGet("[action]")] public void StopBlinking() { _blinkClient.StopBlinking(); } }
Controller actions are just public HTTP based end-points for LED client class.
Of course, LedBlinkClient must be registered in Startup class as we want to get it through constructor of controller and dependency injection.
services.AddSingleton<LedBlinkClient>();
I registered it as a singleton as I don’t want multiple instances of LED client to be created.
Client-side Blazor application
Client-side application in my case contains only Index page and its code-behind class (Blazor supports code-behind files, name it as PageName.razor.cs). Here is the mark-up for Index page.
@page "/" @inherits IndexPage <h1>Hello, blinky!</h1> <p>Led is @BlinkyStatus</p> <div> <button class="btn btn-success" @onclick="@StartBlinking">Start blinking</button> | <button class="btn btn-danger" @onclick="@StopBlinking">Stop blinking</button> </div>
Here is the class for Index page. I keep it as code-behind file so my Blazor page doesn’t contain any logic.
public class IndexPage : ComponentBase { [Inject] public HttpClient HttpClient { get; set; } [Inject] public IJSRuntime JsRuntime { get; set; } public string BlinkyStatus; protected override async Task OnInitializedAsync() { var thisRef = DotNetObjectReference.Create(this); await JsRuntime.InvokeVoidAsync("blinkyFunctions.startBlinky", thisRef); } protected async Task StartBlinking() { await HttpClient.GetStringAsync("/api/Blinky/StartBlinking"); } protected async Task StopBlinking() { await HttpClient.GetStringAsync("/api/Blinky/StopBlinking"); } [JSInvokable] public async Task UpdateStatus() { var isBlinkingValue = await HttpClient.GetStringAsync("/api/Blinky/IsBlinking"); if (string.IsNullOrEmpty(isBlinkingValue)) { BlinkyStatus = "in unknown status"; } else { bool.TryParse(isBlinkingValue, out var isBlinking); BlinkyStatus = isBlinking ? "blinking" : "not blinking"; } StateHasChanged(); } }
Important thing to notice is OnInitializedAsync() method. This method is called when page is opened. It creates Index page reference for JavaScript and starts JavaScript timer to update blinky status periodically.
Updating blinking status automatically
I wasn’t able to get any C# timer work in browser so I went with JavaScript interop. The good old setInterval() with some Blazor tricks made things work. The trick I did is illustrated on the following image.
When page is loaded I use JavaScript interop to send page reference to JavaScript. The reference is saved there and after this timer is created using setInterval() method. After every five seconds timer callback is fired and method to read LED state is called. Yes, this method is defined in Blazor form and it is called from JavaScript with no hacks.
Add some JavaScript file to wwwroot folder of client-side Blazor application and include it in index.html file in same folder. Here’s the content of JavaScript file.
window.blinkyFunctions = { blazorForm: null, startBlinky: function (formInstance) { window.blinkyFunctions.blazorForm = formInstance; setInterval(window.blinkyFunctions.updateStatus, 5000); }, updateStatus: function () { if (window.blinkyFunctions.blazorForm == null) { return; } window.blinkyFunctions.blazorForm.invokeMethodAsync('UpdateStatus'); } }
startBlinky() is the method called from Blazor page. updateSatus() method is called by timer after every five seconds. In timer callback method there’s invokeMethodAsync() call. This is how we can invoke methods of Blazor objects in JavaScript.
Why not updating status in JavaScript? Well, because this post is about Blazor and as much as possible I want to build using Blazor. This is also good temporary solution for Balzor applications that need timer.
Publishing Blazor application to Raspberry Pi
Publishing is currently challenging as client-side Blazor is still under construction and all supportive tooling is not ready yet. There are few tricks I had to figure out the hard way and to save your time I will describe here what I did.
First thing is about assemblies. If there is version downgrade then it’s handled as an error. For some people it worked when they added Nuget packages with downgraded versions to their solution but it didn’t worked out for me. We can ignore warning NU1605 on project level by modifying project file for web application.
<PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> <LangVersion>7.3</LangVersion> <OutputType>Exe</OutputType> <NoWarn>$(NoWarn);NU1605</NoWarn> </PropertyGroup>
NoWarn tag tells tools that this warning must not be handled as an error.
On my Raspberry Pi I want to use port 5000 for my blinky application. To make it happen with minimal efforts I added port binding to Program.cs file of web application.
public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseConfiguration(new ConfigurationBuilder() .AddCommandLine(args) .Build()) .UseUrls("http://*:5000/") .UseStartup<Startup>() .Build(); }
Now it’s time to build the whole solution and then focus on real publishing.
I know, I know – the last and most annoying steps to do before LED starts blinking… It’s also called fun of playing with non-stable technology. So, here’s the dirtiest part of publishing process:
- Publish client-side Blazor application using Visual Studio. Leave all settings like they are as everything should work out well with defaults
- Publish server-side Blazor application on command-line using the following command:
dotnet publish -c Release -r win10-arm /p:PublishSingleFile=true - Open C:\ drive of your Raspberry and create folder Blinky
- Copy files from BlazorHelloBlinky.Server\bin\Release\netcoreapp3.0\win10-arm\ to Blinky folder on Raspberry Pi.
- Open file BlazorHelloBlinky.Client.blazor.config and make it look like here:
c:\Blinky\BlazorHelloBlinky.Client.csproj
BlazorHelloBlinky.Client.dll - Copy wwwroot folder of client-side Blazor app to Blinky folder on Raspberry Pi
- Copy dist folder from folder where client-size Blazor application was published to Blinky folder on Raspberry Pi
- Connect to your Raspberry Pi using PowerShell
- Move to Blinky folder and run BlazorHelloBlinky.Server.exe
- Open browser on your coding box and navigate to http://minwinpc:5000/ (use the name or IP of your Raspberry Pi)
If there were no problems and all required files got to Raspberry then Hello, Blinky should be ready for action.
View Comments (1)
Thanks for putting this together! I particularly like the Blazor tie-in. Another Blazor related project I'd like to see is Blazor and Alexa. Just a thought, thanks again.