**withUrl의 경우, 앱이 구동되는 url(localhost:****)을 적어주시면 되고, 마지막 /plc가 렌더링 될 페이지의 경로라고 생각하시면 됩니다. 이 부분은 앞서 서버쪽에서 마지막 startup.cs부분의 endpoints.MapHub<PlcHub>('/plc') 부분과 동일합니다.
** connection.on("ReceiveData", ~) 에서 데이터를 받는 부분입니다.
가장 중요한 일은 프로젝트를 생성하는 일입니다. 최근엔 Visual Studio에서 ASP.NET CORE로 프로젝트를 생성할때 리액트를 프론트엔드로 선택할 수 있습니다. ASP.NET Core 3.1 기준이라, ASP.NET으로 하는 경우 제대로 안될수도 있음을 미리 알아두시길 바랍니다
2.
프로젝트를 만든후 실행하면, 짜잔- 이렇게 기본 예제가 포함된 프로젝트가 실행됩니다. 프론트엔드 삼대장중 하나인 리액트로 만들어진 프로젝트가 완성되었습니다.
3.
우선 BackEnd쪽을 손대보도록 하겠습니다. Modbus.Net.Core, Modbus.Net.Modbus.Core 이 두가지를 Package Manager(Nuget Manager)로 설치해줍니다. 이 패키지는 모드버스 통신을 하기위함입니다. 그냥 온라인상에 돌아다니는 EasyModbus.Dll을 사용하면 더 편하겠지만, 이게 .net core 용이 아니라 동작이 안되요. 아니면 직접 프로토콜을 구현하셔도 됩니다.
4.
프로젝트 폴더에 Hubs 폴더를 추가하고, PlcHub.cs를 생성합니다.
별다른 내용은 없어도 됩니다. 브라우저와 계속해서 데이터를 주고받는 경우엔 여기서 코드를 추가하시면 됩니다만,
이번의 경우 서버쪽에서 PLC쪽으로 계속해서 데이터를 전달해주기만 하는 예제라, 백그라운드에서 호스트를 돌릴거에요.
나중에 시간이 되면 PLC로 데이터를 전달하는 것도 다루어 보겠습니다.
(안전상의 이유로 웹페이지에서 PLC를 제어하는것은 추천드리지는 않습니다. 그러면서 글 제목이 PLC 제어라는게 아이러니네요. 그래도 모니터링이 된다면 제어도 간단하겠죠?)
5.
프로젝트 하위 폴더에 Models폴더를 생성 후, PlcData.cs를 만들어줍니다.
그리고 어떠한 내용을 전달할지 적어줍니다. 저는 Address와 해당 어드레스의 데이터를 프로퍼티로 잡아보겠습니다.
6.
HostedService를 만들어줘야합니다. HostedService는 서버에서 호스팅 되는 서비스를 백그라운드로 돌려줍니다. 타이머로 주기적인 작업을 요하는 경우에 요긴하게 사용할수 있습니다.
우선 Services 폴더를 프로젝트 하위 폴더에 생성 후 , PlcMonitorHostedService.cs를 만들어줍니다.
코드는 다음과 같습니다. 모드버스는 EasyModbus.dll 이라는 라이브러리를 사용하면 편하지만 .net core 에선 안먹힙니다. 그냥 Nuget Manager에서 Modbus.Net.Modbus.Core를 추가해주세요.
아래코드는 아이템 하나만 읽어 브라우저측으로 보내는 역할을 합니다.
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TestApp.Hubs;
using Modbus.Net;
using Modbus.Net.Modbus;
using TestApp.Models;
namespace TestApp.Services
{
//IHostedService를 상속받으면, 기본적으로 StartAsyc와 StopAsync로 타이머를 시작하고 멈출수 있습니다.
//서버가 시작되면 자동으로 실행됩니다.
//IHubContext<PlcHub>는 SignalR로 통신하기 위한 컨텍스트입니다.
//machine 은 모드버스를 통신하기 위한 부분입니다.
public class PlcMonitorHostedService:IHostedService
{
private Timer _timer;
private readonly IHubContext<PlcHub> _hubContext;
ModbusMachine machine;
// 우선 생성자를 통해, HubContext를 만들어주고, 기본적인 모드버스 설정을 해줍니다.
// 모드버스 어드레스와 아이템 등을요.
public PlcMonitorHostedService(IHubContext<PlcHub> hubContext)
{
_hubContext = hubContext;
if (machine == null)
{
machine = new ModbusMachine(ModbusType.Tcp, "127.0.0.1:502", new List<AddressUnit>()
{
new AddressUnit()
{
Id="1",
Area="4X",
Address=1,
CommunicationTag = "Add1",
DataType=typeof(ushort),
Zoom = 1,
DecimalPos = 0
}
}, 2, 0);
}
machine.KeepConnect = true;
machine.Connect();
}
//StartAsync는 서버가 실행되면 동작합니다. TimeSpan에서 어느 주기로 프로그램을 실행할지 정해줍니다.
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
return Task.CompletedTask;
}
// 실제 timer가 실행할 작업이죠. 어떤 모드버스가 연결되어 있는 경우,
// 해당 데이터를 읽어, SignalR을 통해 접속된 모든 브라우져로 데이터를 보냅니다.
// _hubContext.Clients.All.SendAsync에서 "ReceiveData"는 브라우져측, 즉 React에서 받을 함수의 이름입니다.
private void DoWork(object state)
{
try
{
if (machine.IsConnected)
{
var result = machine.GetDatas(MachineGetDataType.CommunicationTag).MapGetValuesToSetValues();
var data = new PlcData
{
address = "40000",
value = result["Add1"].ToString()
};
_hubContext.Clients.All.SendAsync("ReceiveData", data);
}
else
{
var failure = new PlcData
{
address = "null",
value = "Cannot Read Data"
};
_hubContext.Clients.All.SendAsync("ReceiveData", failure);
}
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
}
// IHostedService를 상속하면서 나온 StopAsync입니다. 서버가 멈추면 동작하죠.
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
}
7.
이제 거의 다 끝났습니다. Startup.cs로 넘어가 컨피규레이션에 이렇게 추가해주시면 됩니다.