OPC UA 활용한 견고한 IIoT 엣지 개발
편집자 주: 산업용 사물인터넷(IIoT)은 산업 운영에 대한 깊은 통찰을 제공하고 연결된 기계와 시스템의 효율성을 높일 것을 약속합니다. 대규모 IIoT 애플리케이션은 계층형 아키텍처에 의존해 광범위한 센서에서 데이터를 수집하고, 데이터를 안정적이고 안전하게 클라우드로 이동하며, 그 통찰과 효율성을 제공하는 데 필요한 분석을 수행합니다. '산업용 인터넷 애플리케이션 개발'에서 저자들은 IIoT 아키텍처를 상세히 검토하고 이러한 시스템과 관련된 광범위한 요구사항을 충족하기 위한 접근법을 논의합니다.
알레나 트라우키나, 자얀트 토마스, 프라샨트 티아기, 키쇼레 레디팔리 저, '산업용 인터넷 애플리케이션 개발'에서 발췌·각색.
3장. IIoT 엣지 개발 (계속) 알레나 트라우키나, 자얀트 토마스, 프라샨트 티아기, 키쇼레 레디팔리 저
산업용 M2M 프로토콜 – OPC UA
이 섹션에서는 라즈베리 파이 허브와 OPC UA 프로토콜을 사용해 센서 시뮬레이터 모듈에서 수신 장치(PC 또는 클라우드)로 데이터를 전송하는 간단한 IoT 앱을 구축해 보겠습니다.
센서 시뮬레이터에서 수신 장치로의 데이터 흐름
OPC UA 프로토콜은 모드버스(Modbus)와 유사하지만 더 많은 데이터 타입을 다루고 심각한 제한이 없으며, 보안, 압축, 낮은 지연 시간을 제공합니다.
이 프로토콜은 OPC 재단에서 산업용 기계 간 통신 프로토콜로 개발했습니다. OPC UA(Unified Architecture)는 오픈 플랫폼 커뮤니케이션즈 (OPC ) 프로토콜의 개선 버전으로, 주요 변경 사항 중 하나는 새 프로토콜이 제한 없이 무료로 제공된다는 점입니다.
다음 표에서 프로토콜에 대한 더 자세한 설명을 확인해 귀하의 필요에 적합한지 판단해 보세요.
키(Key) 값(Value)
오픈 소스 예
OSI 계층 전송 또는 애플리케이션
데이터 타입 정수, 실수, 문자열, 불리언, 날짜, 시간 등
제한사항 복잡한 아키텍처에 부적합
가능한 연산 변수 읽기/쓰기/모니터링/쿼리
지연 시간 낮음
용도 IIoT
보안 예
압축 예
표 5: OPC UA 프로토콜 사양
애플리케이션 구축을 위해 다음이 필요합니다:
필수 소프트웨어
Node.js 6+ (https://nodejs.org/en/download/)
PostgreSQL (https://www.postgresql.org/download/)
Cloud Foundry CLI (https://github.com/cloudfoundry/cli#downloads)
Request (https://www.npmjs.com/package/request)
NodeOPCUA (https://www.npmjs.com/package/node-opcua)
Async (https://www.npmjs.com/package/async)
Docker (https://docs.docker.com/engine/installation/)
필수 하드웨어
라즈베리 파이 3 (모델 B)
전원 어댑터 (2A/5V)
마이크로SD 카드 (8GB+) 및 SD 어댑터
유선 네트워크 연결용 이더넷 케이블
SD 카드 준비
SD 카드 준비 순서는 다음과 같습니다:
최신 라즈비안 라이트 이미지 다운로드 (https://raspberrypi.org/downloads/raspbian/).
SD 카드를 컴퓨터에 연결하고 에처(Etcher, https://www.balena.io/etcher/)로 라즈비안 .img 파일을 SD 카드에 플래싱.
SSH 활성화:
cd /Volumes/boot
touch ssh
Wi-Fi 활성화를 위해 다음 내용으로 wpa_supplicant.conf 생성:
network={
ssid="YOUR_SSID"
psk="YOUR_WIFI_PASSWORD"
}
리눅스 콘솔에서 파일 생성은 GNU nano 에디터 사용. 대부분 리눅스 배포판에 사전 설치. nano 파일명 명령 실행 후 표시된 지시 따름.
/home/pi/hub 생성
/home/pi/hub/package.json 파일 생성, 다음 내용:
{
"name": "hub",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"async": "^2.4.0",
"node-opcua": "0.0.64",
"request": "^2.81.0"
}
}
/home/pi/hub/index.js 파일 생성, 다음 내용으로 REMOTE-SERVER-ADDRESS.com과 REMOTE-SENSOR-ADDRESS를 실제 값으로 교체:
var opcua = require("node-opcua"); var async = require("async");
var request = require("request");
var session, subscription;
var client = new opcua.OPCUAClient();
var sensor = "opc.tcp://REMOTE-SENSOR-ADDRESS:4334/UA/resourcePath";
var receiver = "https://REMOTE-SERVER-ADDRESS.com:8080";
async.series( [
// 연결 수립
function (cb) {
client.connect(sensor, function (err) {
if (err) {
console.log("Connection to " + sensor + "failed");
} else {
console.log("Connection successful");
}
cb(err);
});
},
// 세션 시작
function (cb) {
client.createSession(function (err, res) {
if (!err) session = res;
cb(err);
});
},
// 값 읽기
function (cb) {
session.readVariableValue("ns=1;s=Variable1", function (err, dataValue) {
if (!err) console.log("Variable1 = ", dataValue.value.value);
cb(err);
});
},
// 값 쓰기
function (cb) {
session.writeSingleNode("ns=1;s=Variable1", new opcua.Variant({
dataType: opcua.DataType.Double, value: 100
}), function (err) {
cb(err);
});
},
// 변경 구독
function (cb) {
subscription = new opcua.ClientSubscription(session, {
maxNotificationsPerPublish: 5,
priority: 5,
publishingEnabled: true,
requestedLifetimeCount: 5,
requestedMaxKeepAliveCount: 3,
requestedPublishingInterval: 500,
});
subscription.on("started", function () {
console.log("subscription id: ", subscription.subscriptionId);
}).on("terminated", function () {
cb();
});
setTimeout(function () {
subscription.terminate();
}, 5000);
// 모니터드 아이템 설치
var monitor = subscription.monitor({
attributeId: opcua.AttributeIds.Value,
nodeId: opcua.resolveNodeId("ns=1;s=Variable1"),
},
{
discardOldest: true,
samplingInterval: 50,
queueSize: 5,
},
opcua.read_service.TimestampsToReturn.Both
);
monitor.on("changed", function (dataValue) {
console.log("Variable1 = ", dataValue.value.value);
// 수신기로 전송
var data = {
device: "sensor1",
timestamp: Date.now(),
Variable1: dataValue.value.value
};
request.post({url: receiver, form: data}, function (err) {
if (err) console.log("Failed to send " + JSON.stringify(data) + " to " + receiver);
});
});
},
// 세션 종료
function (cb) {
session.close(function (err) {
if (err) console.log("Failed to close session"); cb();
});
}
],
function (err) { if (err) {
console.log("Failed with error:", err);
} else {
console.log("Successfully finished");
}
client.disconnect(function () {
});
}
);
/home/pi/hub/Dockerfile 파일 생성:
FROM hypriot/rpi-node:boron-onbuild
/home/pi/sensor 생성
/home/pi/sensor/package.json 파일 생성:
{
"name": "sensor",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"node-opcua": "0.0.64"
}
}
/home/pi/sensor/index.js 파일 생성:
var opcua = require("node-opcua");
var min = 1;
var max = 100;
var host = new opcua.OPCUAServer({ buildInfo: {
buildDate: new Date(2018, 8, 8),
buildNumber: "1234",
productName: "productName",
},
port: 4334,
resourcePath: "UA/resourcePath",
});
host.initialize(function () {
var space = host.engine.addressSpace;
var componentOf = space.addObject({
browseName: "browseName",
organizedBy: space.rootFolder.objects,
});
var variable1 = 0;
// 새 값 생성
setInterval(function () {
variable1 = Math.floor(max - Math.random() * (max - min));
}, 500);
space.addVariable({
browseName: "browseName",
componentOf: componentOf,
dataType: "Double",
nodeId: "ns=1;s=Variable1",
value: {
get: function () {
return new opcua.Variant({dataType: opcua.DataType.Double, value: variable1});
},
set: function (variant) {
variable1 = parseFloat(variant.value);
return opcua.StatusCodes.Good;
}
}
});
host.start(function () {
var endpoint = host.endpoints[0].endpointDescriptions()[0].endpointUrl;
console.log("Endpoint: ", endpoint);
});
});
/home/pi/sensor/index.js 파일 처음의 min과 max 값 설정.
/home/pi/sensor/Dockerfile 파일 생성:
FROM hypriot/rpi-node:boron-onbuild