Python get device id is easy.
import uuid
def get_device_id():
return f"BH_{uuid.getnode():012X}"
In C++
DeviceProvider.cpp
#include "DeviceIdProvider.h"
#include <iomanip> // std::setw, std::setfill
#include <sstream> // for std::stringstream
#include <vector>
#include <cstring>
#include <random>
// OS-specific headers
#include <sys/types.h>
#include <sys/socket.h> // 네트워크 인터페이스 접근용
#include <ifaddrs.h> // getifaddrs(), 네트워크 인터페이스 리스트 조회
#include <net/if.h> // IFF_LOOPBACK 플래그 정의
#if defined(__linux__)
#include <netpacket/packet.h> // sockaddr_ll 구조체 (ll = link layer)
#elif defined(__APPLE__)
#include <net/if_dl.h> // sockaddr_dl 구조체 (dl = data link, L2)
#endif
// 6-byte MAC address를 정수형 uint64_t 하나로 변환
// 이더넷, WiFi NIC의 MAC 주소는 6바이트(48비트)
uint64_t DeviceIdProvider::mac_to_uint64(const unsigned char *mac_ptr)
{
uint64_t mac_val = 0;
for (int i = 0; i < 6; i++)
{
// 1바이트(8비트) 왼쪽으로 밀고, 새 바이트를 Append
mac_val = (mac_val << 8) | mac_ptr[i];
}
return mac_val;
}
// 시스템 MAC address 검색 (Python의 uuid.getnode 로직 참조)
uint64_t DeviceIdProvider::get_mac_address_uint64()
{
// 네트워크 인터페이스if (eth0, wlan0, enp45s0 등) 리스트 조회용
struct ifaddrs *ifaddr = nullptr, *ifa = nullptr;
uint64_t candidate_uaa = 0; // 1순위: Universally Administered Address (제조사가 부여한 고유 MAC)
uint64_t candidate_laa = 0; // 2순위: Locally Administered Address (사용자/OS가 임의로 설정한 로컬 MAC)
// 시스템의 모든 네트워크 인터페이스 정보를 ll(링크드리스트) 또는 dl(더블링크드리스트)로 가져옴
if (getifaddrs(&ifaddr) == -1)
{
return 0;
}
// 링크드 리스트 검색
for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr == nullptr)
continue;
// Loopback(127.0.0.1, localhost)은 제외
if (ifa->ifa_flags & IFF_LOOPBACK)
continue;
const unsigned char *mac_ptr = nullptr;
// Memory Overlay로 MAC 주소 추출
#if defined(__linux__)
// AF_PACKET: Linux계열의 L2 링크레이어
if (ifa->ifa_addr->sa_family == AF_PACKET)
{
struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
// 6바이트 MAC 주소인지 확인
// halen(hardware address length)
if (s->sll_halen == 6)
{
// sll_addr 배열로 MAC 주소 위치 얻기
mac_ptr = (const unsigned char *)s->sll_addr;
}
}
#elif defined(__APPLE__)
// AF_LINK: BSD계열의 L2 링크레이어
if (ifa->ifa_addr->sa_family == AF_LINK)
{
struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
// 6바이트 MAC 주소인지 확인
// alen(address length)
if (sdl->sdl_alen == 6)
{
// LLADDR 매크로로 MAC 주소 위치 얻기
mac_ptr = (const unsigned char *)LLADDR(sdl);
}
}
#endif
// Validate MAC address
if (mac_ptr)
{
// 1. Check for 00:00:00:00:00:00 (Invalid)
bool is_zero = true;
for (int i = 0; i < 6; i++)
{
if (mac_ptr[i] != 0)
{
is_zero = false;
break;
}
}
if (is_zero)
continue;
// 2. Check Multicast bit (Odd first byte means multicast, not physical MAC)
// Example: 01:00:5e...
// 유니캐스트가 아닌 멀티캐스트의 MAC은 한 장치의 고유 MAC이 아님
if (mac_ptr[0] & 0x01)
continue;
uint64_t current_mac = mac_to_uint64(mac_ptr);
// 3. Distinguish UAA(Universal) vs LAA(Local)
// 첫 바이트의 두 번째 최하위 비트가 0이면 UAA, 1이면 LAA
bool is_local = (mac_ptr[0] & 0x02);
if (!is_local)
{
// Highest priority인 UAA를 발견 했으니 break;
if (candidate_uaa == 0)
{
candidate_uaa = current_mac;
}
break;
}
else
{
// LAA found, UAA를 찾을 때까지 loop 지속
if (candidate_laa == 0)
{
candidate_laa = current_mac;
}
}
}
}
// getifaddrs로 할당받은 메모리 해제
freeifaddrs(ifaddr);
// Priority: UAA > LAA > 0
if (candidate_uaa != 0)
return candidate_uaa;
if (candidate_laa != 0)
return candidate_laa;
return 0; // MAC not found
}
// RFC 4122(UUID 프로토콜) compliant random node ID generation
uint64_t DeviceIdProvider::generate_random_node_id()
{
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint64_t> dis(0, 0xFFFFFFFFFFFF); // 6바이트(48비트) 범위
uint64_t random_node = dis(gen);
// RFC 4122
// 41번째(0...47)비트인 멀티캐스트 비트를 1로 설정하여 Locally Administered Address임을 표시
// UUID의 경우, MAC 주소가 아님을 멀티캐스트 비트에 명시해야함.
random_node |= (uint64_t(1) << 40);
return random_node;
}
std::string DeviceIdProvider::getDeviceId()
{
// 1. MAC 주소 시도
uint64_t node_id = get_mac_address_uint64();
// 2. 실패시 UUID 생성
if (node_id == 0)
{
node_id = generate_random_node_id();
}
// 3. 포맷팅 (BH_XXXXXXXXXXXX)
// 16진수, UPPERCASE, 12자리, 빈공간 0으로 채움
std::stringstream ss;
ss << "BH_" << std::hex << std::uppercase << std::setw(12) << std::setfill('0') << node_id;
return ss.str();
}
DeviceIdProvider.h
#pragma once
#include <string>
#include <cstdint>
class DeviceIdProvider
{
public:
static std::string getDeviceId();
private:
static uint64_t get_mac_address_uint64();
static uint64_t mac_to_uint64(const unsigned char *mac_ptr);
static uint64_t generate_random_node_id();
};
CMakeLists.txt
# bh_device_id라는 라이브러리 생성
add_library(bh_device_id
DeviceIdProvider.cpp
DeviceIdProvider.h
)
# CMAKE_CURRENT_SOURCE_DIR: 현재 폴더
target_include_directories(bh_device_id PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_executable(get_device_id main_tool.cc) # python에서 사용할 수 있게 main 생성
target_link_libraries(get_device_id PRIVATE bh_device_id) # bh_device_id 라이브러리를 링크
# Python 스크립트에서 실행할 수 있게, 컴파일 위치 세팅
# main.spec안에 binaries=[('../../bin/get_device_id', '.')] 로 from.. to를 지정
set_target_properties(
get_device_id
PROPERTIES # 속성 설정 명시
RUNTIME_OUTPUT_DIRECTORY # 속성 이름 및 경로를 나타내는 키
"${PROJECT_SOURCE_DIR}/bin" # 실제 값, 프로젝트 최상위 폴더 밑의 bin 폴더
)
main_tool.cc
#include "DeviceIdProvider.h"
#include <iostream>
int main() {
std::cout << DeviceIdProvider::getDeviceId() << std::endl;
return 0;
}
파이썬에서는
def get_base_dir():
# When frozen (e.g., PyInstaller), resolve paths relative to the executable.
if getattr(sys, "frozen", False):
if hasattr(sys, "_MEIPASS"):
return sys._MEIPASS
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.abspath(__file__))
def get_robot_id():
# Only runs in PyInstaller bundle where get_device_id is at root
# get_base_dir는 frozen(실행파일) 상태면, sys._MEIPASS를 반환
# 만약 스크립트로 그냥 실행하는 거라면, 현재 위치를 반환
bin_path = os.path.join(get_base_dir(), "get_device_id")
# C++ 코드 실행
if os.path.exists(bin_path):
try:
output = subprocess.check_output([bin_path], text=True).strip()
if output:
return output
except Exception as e:
print(f"[WARNING] C++ Tool execution failed: {e}")
raise Exception(
"C++로 부터 로봇 ID를 가져오지 못했습니다."
)
Top comments (0)