티스토리 뷰
최근에 BEMS(Building Energy Management System)를 접하면서 전통적인 모드버스(ModBUS) 프로토콜과 함께 자주 접하는 프로토콜이 있다면 바로 BACnet(이후 백넷으로 기술)이다. 모드버스가 등장한 것이 1979년이고 백넷이 등장한 것이 1987년이니까 약 십 년의 차이가 있다. 모드버스가 슈나이더라는 민간 회사에서 만들어 이후로 표준화가 진행되었다면, 백넷은 ASHRAE(American Society of Heating, Refrigerating and Air-Conditioning Engineers)라는 협회에서 시작되어 ANSI, ISO, 유럽 표준으로 채택되었다. 협회의 이름에서도 알 수 있듯이 건물의 냉난방, 환기, 공조 시스템 전문가들이 모여서 건물 자동화와 제어(BAC, Building Automation and Control)를 위한 프로토콜을 만든 것이 BACnet(백넷)이다.
백넷의 이해를 위한 그림을 보면 TCP/IP 4 계층과 유사함을 알 수 있다. 우선 맨 아래 단계의 물리 레벨을 보면 전통적인 이더넷, RS-232, RS-485와 함께 빌딩의 조명이나 공조, 환기 시스템에 많이 적용된다는 LonTalk, 근거리 무선망 구축에 사용하는 지그비(Zigbee)까지 볼 수 있다.
최상단 응용 레이어에서는 다양한 서비스를 정의하고 있는데 TCP의 서비스 이름 ftp, http, smtp 등과 비교해 보면 대표적인 서비스 이름만 보아도 매우 직관적이다.
Who-Is, I-Am, Who-Has, I-Have, Read-Property, Write-Property
그리고, 각 서비스에서 사용하는 오브젝트 60개도 구체적으로 정의해 놓았는데 이를 보면 백넷으로 무엇을 하려는 것인지 대충 감이 온다.
Access Credential, Access Door, Access Point, Access Rights, Access User, Access Zone, Accumulator, Alert Enrollment, Averaging, Calendar, Channel, Command, Device, Elevator Group, Escalator, Life Safety Point, Life Safety Zone, Lift, Load Control, Loop, Network Port, Network Security, Notification Class, Notification Forwarder, Program, Pulse Converter, Schedule, Structured View, Timer, Trend Log, Trend Log Multiple
Analog Input, Analog Output, Analog Value, Binary Input, Binary Lighting Output, Binary Output, Binary Value, BitString Value, CharacterString Value, Credential Data Input, Date Pattern Value, Date Value, DateTime Pattern Value, DateTime Value, Integer Value, Large Analog Value, Lighting Output, Multi-state Input, Multi-state Output, Multi-state Value, Octetstring Value, Positive Integer Value, Time Pattern Value, Time Value
// Setting up BACnet port
const UInt16 SETTING_BACNET_PORT = 47808;
// Device Instance
const UInt32 SETTING_BACNET_DEVICE_INSTANCE = 389000;
// Downstream IP
const string SETTING_BACNET_SERVER_IP_ADDRESS = "192.168.1.26";
private void CheckUserSubOption(ConsoleKey input)
{
byte[] connectionStringAsBytes = CreateConnectionString(new IPEndPoint(IPAddress.Parse(SETTING_BACNET_SERVER_IP_ADDRESS), SETTING_BACNET_PORT));
byte* connectionStringPointer = PointerData(connectionStringAsBytes);
switch (subOption)
{
case ConsoleKey.D:
switch (input)
{
case ConsoleKey.L:
Console.WriteLine("FYI: Sending a Local Broadcast WhoIs message");
CASBACnetStackAdapter.SendWhoIs(connectionStringPointer, (byte)connectionStringAsBytes.Length, CASBACnetStackAdapter.NETWORK_TYPE_IP, true, 0, null, 0);
break;
case ConsoleKey.W:
Console.WriteLine("FYI: Sending a Local Broadcast WhoIs message with limits of devices with device instances between 389900 and 389999");
CASBACnetStackAdapter.SendWhoIsWithLimits(389900, 389999, connectionStringPointer, (byte)connectionStringAsBytes.Length, CASBACnetStackAdapter.NETWORK_TYPE_IP, true, 0, null, 0);
break;
case ConsoleKey.R:
Console.WriteLine("FYI: Sending a Remote Broadcast WhoIs message to DNET 7");
CASBACnetStackAdapter.SendWhoIs(connectionStringPointer, (byte)connectionStringAsBytes.Length, CASBACnetStackAdapter.NETWORK_TYPE_IP, true, 7, null, 0);
break;
case ConsoleKey.G:
Console.WriteLine("FYI: Sending a Global Broadcast WhoIs message");
CASBACnetStackAdapter.SendWhoIs(connectionStringPointer, (byte)connectionStringAsBytes.Length, CASBACnetStackAdapter.NETWORK_TYPE_IP, true, 0xFFFF, null, 0);
break;
case ConsoleKey.Q:
Console.WriteLine("FYI: Exiting the WhoIs menu");
subOption = ConsoleKey.NoName;
break;
default:
Console.WriteLine("You pressed the '{0}' key. Not an assigned WhoIs option", input);
Console.WriteLine("\nHelp:");
Console.WriteLine(" L - Send a Local Broadcast WhoIs message");
Console.WriteLine(" W - Send a Local Broadcast WhoIs message with Limits");
Console.WriteLine(" R - Send a Remote Broadcast WhoIs message");
Console.WriteLine(" G - Send a Global Broadcast WhoIs message");
Console.WriteLine(" Q - Exit WhoIs menu");
break;
}
break;
default:
break;
}
}
// Send readproperty service request
private void ReadProperty()
{
byte[] connectionStringAsBytes = CreateConnectionString(new IPEndPoint(IPAddress.Parse(SETTING_BACNET_SERVER_IP_ADDRESS), SETTING_BACNET_PORT));
byte* connectionStringPointer = PointerData(connectionStringAsBytes);
Console.WriteLine("FYI: Sending ReadProperty message");
CASBACnetStackAdapter.BuildReadProperty(CASBACnetStackAdapter.OBJECT_TYPE_ANALOG_INPUT, 0, CASBACnetStackAdapter.PROPERTY_IDENTIFIER_PRESENT_VALUE, false, 0);
CASBACnetStackAdapter.SendReadProperty(null, connectionStringPointer, (byte)connectionStringAsBytes.Length, CASBACnetStackAdapter.NETWORK_TYPE_IP, 0, null, 0);
}
위의 코드는 C#으로 백넷의 whois와 readproperty 서비스를 UDP 기반으로 요청하는 예제 코드이다. 프로그래머 입장에서는 백넷 프로토콜을 구현한 라이브러리를 활용하여 필요한 백넷 서비스를 잘 사용하면 된다. 관련하여 참고할만한 무료 및 오픈소스 라이브러리와 프로그램도 상당수 찾을 수 있었다.
위의 그림은 소스포지(sf.net)에서 "bacnet"으로 프로젝트를 검색한 결과로 "Yet Another Bacnet Explorer"에 대한 사용자들의 다운로드 횟수를 보면 엄청난 인기를 미루어 짐작할 수 있다. 웬만한 도구 이상이다. C# 코드를 배포하고 있고 최근까지도 업데이트가 이루어지고 있다. 라이선도 MIT License로 상당히 개방적이다.
위의 그림은 깃허브(github)에서 "bacnet library"로 검색한 결과로 C/C++, C#, 파이썬등 다양한 언어로 백넷 라이브러리가 배포되고 있음을 확인할 수 있었다.
'IT 일반' 카테고리의 다른 글
크레이트 DBMS(CrateDB)와 DBaaS 그리고 SaaS (0) | 2023.08.22 |
---|---|
DNP3 프로토콜에 대한 고찰 (0) | 2023.08.18 |
제로 MQ와 래빗 MQ에 대한 고찰 (0) | 2023.06.19 |
PDF 문서와 악성코드 문제 (0) | 2023.03.03 |
오픈 소스 ERP에 대한 가능성 검토 (1) | 2019.05.14 |