PLCnext Control gRPC 서버용 C# 클라이언트 만들기: 단계별 가이드
펌웨어 버전 2022.0 LTS부터 AXC F 1152와 AXC F 2152 장치에 gRPC 서버가 도입되었습니다. 이 gRPC 서버는 클라이언트 애플리케이션이 PLCnext Control 장치의 RSC 서비스에 접근할 수 있는 방법을 제공합니다. 클라이언트 애플리케이션은:
- gRPC를 지원하는 모든 언어로 작성 가능
- 어디서든 실행 가능 - PLCnext Control 장치 로컬 또는 원격 머신*
- OCI 컨테이너에서 호스팅 가능
(* 원격 액세스는 펌웨어 버전 2022.3부터 가능)
PLCnext Control 장치의 gRPC 서버에 대한 일반 정보는 PLCnext Info Center에서 확인할 수 있습니다.
이 문서에서는 PLCnext Control 장치에서 실행되는 간단한 C# gRPC 클라이언트 애플리케이션을 만드는 방법을 설명합니다.
사전 요구 사항
아래 절차는 다음을 사용합니다:
- 펌웨어 버전 2022.0.3 LTS 이상이 실행되는 AXC F 2152
- PLCnext Engineer 버전 2022.0.1 LTS 이상
- Visual Studio 2019 (모든 에디션)
- 이 메이커 블로그 게시물에서 소개된 개념: C#으로 간단한 PLCnext 콘솔 애플리케이션 만드는 방법
- PLCnext/gRPC GitHub 저장소의 protobuf 디렉토리에 있는 gRPC 서버용 Protobuf 정의 파일
- Device Status RSC 서비스
- Data Access RSC 서비스
절차
1. 웹 기반 관리 페이지를 사용하여 'GRPC LOCAL SERVER'라는 서비스가 활성화되어 있는지 확인합니다.
2. 타겟 장치용 새 PLCnext Engineer 프로젝트를 생성합니다. 이 프로젝트에는 다음이 있어야 합니다:
- AI1이라는 OUT 포트 변수가 있는 프로그램 (타입: INT)
- 해당 프로그램의 인스턴스인 MainInstance1
3. PLCnext Engineer 프로젝트를 타겟 장치로 전송합니다.
4. Visual Studio에서 이전 메이커 블로그 게시물의 1-3단계 절차에 따라 빈 C# 콘솔 애플리케이션을 생성합니다.
5. 프로젝트 속성 창에서 프로젝트 타겟 프레임워크를 '.NET 5.0'으로 설정합니다.
6. 솔루션 탐색기에서 솔루션을 마우스 오른쪽 버튼으로 클릭하고 '솔루션용 NuGet 패키지 관리...'를 선택한 후 다음 NuGet 패키지를 설치합니다:
- Grpc.Tools
- Grpc.Net.Client
- Google.Protobuf
7. Protobuf 정의 파일이 들어 있는 protobuf 폴더를 프로젝트 소스 폴더에 복사합니다. Protobuf는 gRPC 서비스를 설명하는 데 사용되는 인터페이스 정의 언어(IDL)입니다.
8. 프로젝트 구성 파일에 프로젝트에서 사용할 서비스에 대한 .proto 파일에 대한 참조를 추가합니다. 프로젝트 구성의 ItemGroup 섹션은 다음과 비슷하게 보일 것입니다:
9. 프로젝트 .cs 파일의 내용을 다음 코드로 교체합니다 (네임스페이스 이름은 변경해야 할 수 있습니다):
using System;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using Grpc.Net.Client;
using Arp.Device.Interface.Services.Grpc;
using Arp.Plc.Gds.Services.Grpc;
namespace ConsoleApp1 {
class Program {
static void Main() {
// Unix 도메인 소켓 연결 코드는 다음에서 가져옴:
// https://docs.microsoft.com/en-us/aspnet/core/grpc/interprocess?view=aspnetcore-6.0
var udsEndPoint = new UnixDomainSocketEndPoint("/run/plcnext/grpc.sock");
var connectionFactory = new UnixDomainSocketConnectionFactory(udsEndPoint);
var socketsHttpHandler = new SocketsHttpHandler { ConnectCallback = connectionFactory.ConnectAsync };
// PLCnext 유닉스 소켓에 대한 gRPC 채널 생성
using var channel = GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions { HttpHandler = socketsHttpHandler });
// 해당 채널에서 Device Status Service용 gRPC 클라이언트 생성
var grpc_status_client = new IDeviceStatusService.IDeviceStatusServiceClient(channel);
// 해당 채널에서 Data Access Service용 gRPC 클라이언트 생성
var grpc_data_client = new IDataAccessService.IDataAccessServiceClient(channel);
// Device Status Service에서 가져올 항목 생성
// 항목 식별자는 PLCnext Info Center에 나열됨:
// https://www.plcnext.help/te/Service_Components/Remote_Service_Calls_RSC/RSC_device_interface_services.htm#IDeviceStatusService
var item = new IDeviceStatusServiceGetItemRequest();
item.Identifier = "Status.Board.Temperature.Centigrade";
// Data Access Service에서 가져올 변수 생성
var data = new IDataAccessServiceReadSingleRequest();
data.PortName = "Arp.Plc.Eclr/MainInstance1.AI1";
// 응답 변수
IDeviceStatusServiceGetItemResponse grpc_status_response;
IDataAccessServiceReadSingleResponse grpc_data_response;
// 무한 루프
while (true) {
// Device Status Service에 항목 요청
grpc_status_response = grpc_status_client.GetItem(item);
// Data Access Service에 데이터 요청
grpc_data_response = grpc_data_client.ReadSingle(data);
// 결과 보고
var temperature = grpc_status_response.ReturnValue.Int8Value;
var ai1 = grpc_data_response.ReturnValue.Value.Int16Value;
Console.WriteLine("Board Temperature = " + temperature + "°C");
Console.WriteLine("MainInstance1.AI1 = " + ai1);
// 1초 대기
Thread.Sleep(1000);
}
}
}
public class UnixDomainSocketConnectionFactory {
private readonly EndPoint _endPoint;
public UnixDomainSocketConnectionFactory(EndPoint endPoint) {
_endPoint = endPoint;
}
public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _, CancellationToken cancellationToken = default) {
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
try {
await socket.ConnectAsync(_endPoint, cancellationToken).ConfigureAwait(false);
return new NetworkStream(socket, true);
}
catch {
socket.Dispose();
throw;
}
}
}
}
10. 솔루션 탐색기에서 솔루션을 마우스 오른쪽 버튼으로 클릭하고 터미널을 엽니다.
11. 터미널에서 다음 명령어를 실행합니다:
dotnet build ConsoleApp1.csproj
dotnet publish -c RELEASE -r linux-arm .\ConsoleApp1.csproj -o MyApp
... 여기서 ConsoleApp1은 솔루션 이름이고, MyApp은 애플리케이션이 게시될 출력 디렉토리 이름입니다. 필요시 DEBUG 모드로도 게시할 수 있습니다.
12. 출력 디렉토리와 그 내용을 PLC로 복사합니다 (예: scp 또는 WinSCP 사용).
scp -r MyApp admin@192.168.1.10:~
13. PLC에서 셸 세션을 엽니다 (예: ssh 또는 PuTTY 사용).
14. 실행 파일에 실행 권한이 있는지 확인합니다:
$ chmod a+x /opt/plcnext/MyApp/ConsoleApp1
15. 애플리케이션을 실행합니다:
$ /opt/plcnext/MyApp/ConsoleApp1
출력은 다음과 비슷해야 합니다:
Board Temperature = 50°C
MainInstance1.AI1 = 0
Board Temperature = 50°C
MainInstance1.AI1 = 0
Board Temperature = 50°C
MainInstance1.AI1 = 0
Board Temperature = 50°C
MainInstance1.AI1 = 0
16. PLCnext Engineer에서 온라인 상태로 전환하고 AI1 변수의 값을 변경합니다. 애플리케이션이 보고하는 값이 변경되어야 합니다.
참조 및 기타 리소스
1. .NET Core ❤ gRPC
2. .NET 클라이언트로 gRPC 서비스 호출
3. gRPC 유용한 리소스 모음