2

I am new in Blazor and I am trying to create an app reading from a serial port. At first, I created a console application using .Core 3.1 Everything worked fined so I moved the code to Blazor and although when I put debug point I can see that the code works I cannot make the UI to show the value

@using System;
@using System.Diagnostics;
@using System.IO.Ports;
@using System.Threading;


<head>
    <title>Main Scale Monitor</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="/css/all.css" rel="stylesheet">
    <link href="/css/stylesheet.css" rel="stylesheet">
</head>


<div class="scale-monitor">
    <div class="digital-monitor net-weight grid-line">
        <div class="digital-monitor-left net-left">NET</div>
        <div class="digital-monitor-center net-center" bind="@ScaleValue"></div>
        <div class="digital-monitor-right net-right">Kg</div>
    </div>
</div>


@code {

    public string ScaleValue { get; set; } = "0.001";

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            System.IO.Ports.SerialPort mySerialPort = new System.IO.Ports.SerialPort("COM3");

            mySerialPort.BaudRate = 9600;
            mySerialPort.Parity = System.IO.Ports.Parity.None;
            mySerialPort.StopBits = System.IO.Ports.StopBits.One;
            mySerialPort.DataBits = 8;
            mySerialPort.Handshake = System.IO.Ports.Handshake.None;

            mySerialPort.NewLine = (@"&N");

            mySerialPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(DataReceivedHandler);

            if (!mySerialPort.IsOpen)
            {
                mySerialPort.Open();

            }
        }
    }


    private void DataReceivedHandler(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        System.IO.Ports.SerialPort sp = (System.IO.Ports.SerialPort)sender;

        try
        {
            string indata = sp.ReadLine();
            ScaleValue = indata.ToString(); //WHEN I PUT DEBUG HERE I CAN SEE THE CORRECT VALUE

        }
        catch (Exception ex)
        {
            string msg = ex.Message;
        }

    }
}


<style>

    *, ::after, ::before {
        box-sizing: unset;
    }

    .grid-line {
        border: 1px solid black;
    }

    .scale-monitor {
        display: grid;
        grid-template-columns: 35% 35% 30%;
        grid-template-rows: 50% 50%;
        width: 100%;
        height: 90%;
        margin: 5px;
        width: 1020px;
    }


    /*#region generic*/

    .digital-monitor {
        background: #000000;
        color: greenyellow;
        text-align: right;
        font-family: digitalregular;
        display: grid;
        grid-template-columns: repeat(10, 1fr);
        grid-template-rows: repeat(5, 1fr);
        width: 95%;
        height: 95%;
        margin: 5px;
        border-radius: 15px;
        align-items: center;
    }

    /*#region generic scalemonitor*/
    .digital-monitor-left {
        writing-mode: vertical-rl;
        opacity: 0.5;
        grid-row: 1 / span 5;
        grid-column: 1;
        text-align: center;
        grid-column: 1;
        transform: rotate(180deg);
    }

    .digital-monitor-center {
        grid-row: 2 / span 3;
        grid-column: 2/ span 7;
        text-align: right;
        vertical-align: bottom;
    }

    .digital-monitor-right {
        grid-row: 2 / span 3;
        grid-column: 9/ span 2;
        text-align: right;
        opacity: 0.75;
        margin-bottom: 0.4em;
        margin-right: 3px;
        align-self: self-end;
    }
    /*#endregion*/

    /*#endregion*/


    /*#region NET*/
    .net-weight {
        grid-column: 2;
        grid-row: 1 / span 2;
        height: 98%;
    }

    .net-left {
        font-size: 40px;
    }

    .net-center {
        font-size: 100px;
    }

    .net-right {
        font-size: 40px;
    }

    /*#endregion NET*/
</style>

Any idea of how I could do the correct binding?

Nianios
  • 1,391
  • 3
  • 20
  • 45

4 Answers4

2

From the Blazor Lifecycle documentation: "Asynchronous work immediately after rendering must occur during the OnAfterRenderAsync lifecycle event.

Even if you return a Task from OnAfterRenderAsync, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. It's different from the other lifecycle methods, which schedule a further render cycle once the returned task completes."

Try to call the StateHasChanged() manually after the ScaleValue is set.

Artak
  • 2,819
  • 20
  • 31
  • When I call the StateHasChanged I get : The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state. DO you have any good examples for this? – Nianios Mar 14 '20 at 23:36
  • Will try this out now, and get back to you. – Artak Mar 14 '20 at 23:40
  • Can you try this: await InvokeAsync(() => StateHasChanged()); – Artak Mar 14 '20 at 23:47
  • Of course this would require to change the event handle to `async` – Artak Mar 14 '20 at 23:48
  • @Artak and @ Nianos: I think it is better (safer) to keep the event as-is and just fire that InvokeAsync() without an await. – H H Mar 15 '20 at 20:28
  • There is no much difference, actually, because your event handler `DataReceivedHandler` is already `void`. That said, However, if you not await on the `InvokeAsync` call, I think you will receive a compiler warning about it. That said, I would go with the `await InvokeAsync` option. – Artak Mar 16 '20 at 00:56
2

I have found an answer,
I am not 100% this is the best solution

Create a service

public class ReadSerialPortService
{
    public string SerialPortValue { get; set; }

    public ReadSerialPortService()
    {
        System.IO.Ports.SerialPort mySerialPort = new System.IO.Ports.SerialPort("COM3");

        mySerialPort.BaudRate = 9600;
        mySerialPort.Parity = System.IO.Ports.Parity.None;
        mySerialPort.StopBits = System.IO.Ports.StopBits.One;
        mySerialPort.DataBits = 8;
        mySerialPort.Handshake = System.IO.Ports.Handshake.None;

        mySerialPort.NewLine = (@"&N");

        if (!mySerialPort.IsOpen)
        {
            mySerialPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(DataReceivedHandler);
            mySerialPort.Open();
        }
    }

    private void DataReceivedHandler(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        System.IO.Ports.SerialPort sp = (System.IO.Ports.SerialPort)sender;
        double scaleDec = 0.00;
        try
        {
            string indata = sp.ReadLine();
            SerialPortValue = indata.ToString();
        }
        catch (Exception ex)
        {
            string msg = ex.Message;
        }

    }

    public Task<string> GetSerialValue()
    {
        return Task.FromResult(SerialPortValue);
    }
}

And then in the component

@inject ReadSerialPortService _readSerialPortService
<head>
   <title>Main Scale Monitor</title>
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link href="/css/all.css" rel="stylesheet">
   <link href="/css/stylesheet.css" rel="stylesheet">
</head>

<div class="scale-monitor">
   <div class="digital-monitor net-weight grid-line" @onclick="GetValue">
      <div class="digital-monitor-left net-left">NET</div>
      <div class="digital-monitor-center net-center">@ScaleValue</div>
      <div class="digital-monitor-right net-right">Kg</div>
   </div>
</div>


@code {
private int Count { get; set; } = 100;
Timer _updateTimer;
public string ScaleValue { get; set; } = "0.000";    

protected override async Task OnInitializedAsync()
{
    ScaleValue = await _readSerialPortService.GetSerialValue();
    _updateTimer = new Timer(state => { InvokeAsync(GetValue); }, null, 0, 100);
}


public async Task GetValue()
{
    ScaleValue = await _readSerialPortService.GetSerialValue();
    await InvokeAsync(() => StateHasChanged());
}

public void Dispose()
{
    _updateTimer.Dispose();
}

}

Nianios
  • 1,391
  • 3
  • 20
  • 45
  • You can remove all the async (Task, await) stuff from GetValue() and GetSerialValue(), they are just polling and that is fast and synchronous. Be aware that a string is 'thread-safe' , you can't share more complex data (double, decimal) this way. – H H Mar 17 '20 at 07:07
  • Hello @Nianios, I Implemented your solution and it's working fine for local server, after pushing code on aws server it's giving me console error "Access to the port 'COM3' is denied" Can you help for above issue? – PK Yadav Nov 03 '22 at 04:28
0

Your problem sits where you try and bind the value to the div.

The div element is not a component and will not support the bind attribute.

Try this: <div class="digital-monitor-center net-center">@ScaleValue</div>

Since your code is not relying on the render of the page, set it to use another lifecycle method.

Try putting it in protected override void OnInitialized() { }

Edit:

Read up on Blazor's lifecycles here: https://learn.microsoft.com/en-us/aspnet/core/blazor/lifecycle?view=aspnetcore-3.1

Edit: Code Example

@using System;
@using System.Diagnostics;
@using System.IO.Ports;
@using System.Threading;

@page "/Test";


<head>
    <title>Main Scale Monitor</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="/css/all.css" rel="stylesheet">
    <link href="/css/stylesheet.css" rel="stylesheet">
</head>


<div class="scale-monitor">
    <div class="digital-monitor net-weight grid-line">
        <div class="digital-monitor-left net-left">NET</div>
        <div class="digital-monitor-center net-center">@ScaleValue</div>
        <div class="digital-monitor-right net-right">Kg</div>
    </div>
</div>


@code {

    public string ScaleValue { get; set; } = "0.001";

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
        }
    }

    protected override void OnInitialized()
    {

        System.IO.Ports.SerialPort mySerialPort = new System.IO.Ports.SerialPort("COM3");

        mySerialPort.BaudRate = 9600;
        mySerialPort.Parity = System.IO.Ports.Parity.None;
        mySerialPort.StopBits = System.IO.Ports.StopBits.One;
        mySerialPort.DataBits = 8;
        mySerialPort.Handshake = System.IO.Ports.Handshake.None;

        mySerialPort.NewLine = (@"&N");

        mySerialPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(DataReceivedHandler);

        ScaleValue = "test";

        //I skipped this part with a return, since I don't have COM3 locally

        if (!mySerialPort.IsOpen)
        {
            mySerialPort.Open();

        }
    }


    private void DataReceivedHandler(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        System.IO.Ports.SerialPort sp = (System.IO.Ports.SerialPort)sender;

        try
        {
            string indata = sp.ReadLine();
            ScaleValue = indata.ToString(); //WHEN I PUT DEBUG HERE I CAN SEE THE CORRECT VALUE

        }
        catch (Exception ex)
        {
            string msg = ex.Message;
        }

    }
}


<style>

    *, ::after, ::before {
        box-sizing: unset;
    }

    .grid-line {
        border: 1px solid black;
    }

    .scale-monitor {
        display: grid;
        grid-template-columns: 35% 35% 30%;
        grid-template-rows: 50% 50%;
        width: 100%;
        height: 90%;
        margin: 5px;
        width: 1020px;
    }


    /*#region generic*/

    .digital-monitor {
        background: #000000;
        color: greenyellow;
        text-align: right;
        font-family: digitalregular;
        display: grid;
        grid-template-columns: repeat(10, 1fr);
        grid-template-rows: repeat(5, 1fr);
        width: 95%;
        height: 95%;
        margin: 5px;
        border-radius: 15px;
        align-items: center;
    }

    /*#region generic scalemonitor*/
    .digital-monitor-left {
        writing-mode: vertical-rl;
        opacity: 0.5;
        grid-row: 1 / span 5;
        grid-column: 1;
        text-align: center;
        grid-column: 1;
        transform: rotate(180deg);
    }

    .digital-monitor-center {
        grid-row: 2 / span 3;
        grid-column: 2/ span 7;
        text-align: right;
        vertical-align: bottom;
    }

    .digital-monitor-right {
        grid-row: 2 / span 3;
        grid-column: 9/ span 2;
        text-align: right;
        opacity: 0.75;
        margin-bottom: 0.4em;
        margin-right: 3px;
        align-self: self-end;
    }
    /*#endregion*/

    /*#endregion*/


    /*#region NET*/
    .net-weight {
        grid-column: 2;
        grid-row: 1 / span 2;
        height: 98%;
    }

    .net-left {
        font-size: 40px;
    }

    .net-center {
        font-size: 100px;
    }

    .net-right {
        font-size: 40px;
    }

    /*#endregion NET*/
</style>
Marius
  • 1,420
  • 1
  • 11
  • 19
  • The
    @ScakeValue
    doesn't work neither works the OnInitialized.
    – Nianios Mar 14 '20 at 23:34
  • I included the code you supplied with my recommended changes, and it works fine. Check for my latest edit. – Marius Mar 14 '20 at 23:40
  • @Nianios, What do you mean by it doesn't work? What doesn't work? – Marius Mar 17 '20 at 09:18
  • First, the OnInitalized is crashed during the second pass, because the serial port is already under reading mode (the check for IsOpen doesn't work) Even if I jump this point, the UI does not updated – Nianios Mar 17 '20 at 21:00
0

Thank you for this posting. It really helps me.

I am using .NET Core 3.1 building Blazor app.

"system.io.ports.serialport" is not incuded in the standard .NET Core. It is an extension package. To install, please check this posting https://stackoverflow.com/a/64786623/6192940.

coarist
  • 789
  • 9
  • 11