번개애비의 라이프스톼일
Arduino Messagepack(msgpack)에서 다차원배열과 다양한 데이터형태를 Unpack 처리하는 방법 본문
최근 서버와 Arduino간 Websocket을 통해 실시간 통신 프로토콜을 개발하는 과정에서
MessagePack의 데이터처리에 이슈가 있어서 골머리를 앓다가 문제를 해결하여 이렇게 포스팅으로 남긴다.
[참고]
MessagePack은 JSON과 같이 Serialize를 지원하면서도 JSON보다 빠르고 짧은 데이터를 전송할 수 있는 장점이 있다.
먼저, 문제의 원인
서버와 같이 고오급 언어를 사용하는 환경에서는 Array안에 Key에 따라 Value의 데이터형을 다르게 가져갈 수 있다.
하지만, Arduino와 같이 C Language 베이스의 언어는 Array를 선언할때
반드시 Key와 Value의 데이터형을 지정해야하는 만큼 Key에 따라 달라지는 데이터형을 지원할 수 없다.
아래 예시 사례를 보자
//아두이노는 Value가 모두 String과 같이 하나의 데이터형만 Array든 Map이든 담을 수 있다.
{
"output": "시리얼 출력",
"is_success": "true",
"redirect": "/ws/test1",
"__CORE_EXEC_TYPE__": "WS",
"timezone": "Asia/Seoul",
"geocode": "KR",
"command": "1\n1"
}
//Arduino에서 처리불가능하지만 다른 고급언어는 처리가능
{
"output": "시리얼 출력",
"is_success": true,
"redirect": false,
"__CORE_EXEC_TYPE__": "WS",
"timezone": "Asia/Seoul",
"geocode": "KR",
"command": 474.0001
}
MessagePack 공식홈페이지 (https://msgpack.org/) 에 제시된 hideakitai의 MsgPack 라이브러리를 참고하여
아래와 같이 예시코드를 작성하면 다음과 같은데 문제는 MsgPack::map_t<String, String> rx_map; 과 같이
Key와 Value 모두 "String" 타입의 데이터만 불러올 수 있는 문제점이 있었다.
#include <Arduino.h>
#include <MsgPack.h>
uint8_t packed_data[] = {
0x87, 0xA6, 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0xB0, 0xEC, 0x8B, 0x9C, 0xEB, 0xA6, 0xAC, 0xEC, 0x96, 0xBC, 0x20, 0xEC, 0xB6, 0x9C, 0xEB, 0xA0, 0xA5, 0xAA, 0x69, 0x73, 0x5F, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0xC3, 0xA8, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0xA9, 0x2F, 0x77, 0x73, 0x2F, 0x74, 0x65, 0x73, 0x74, 0x31, 0xB2, 0x5F, 0x5F, 0x43, 0x4F, 0x52, 0x45, 0x5F, 0x45, 0x58, 0x45, 0x43, 0x5F, 0x54, 0x59, 0x50, 0x45, 0x5F, 0x5F, 0xA2, 0x57, 0x53, 0xA8, 0x74, 0x69, 0x6D, 0x65, 0x7A, 0x6F, 0x6E, 0x65, 0xAA, 0x41, 0x73, 0x69, 0x61, 0x2F, 0x53, 0x65, 0x6F, 0x75, 0x6C, 0xA7, 0x67, 0x65, 0x6F, 0x63, 0x6F, 0x64, 0x65, 0xA2, 0x4B, 0x52, 0xA7, 0x63, 0x6F, 0x6D, 0x6D, 0x61, 0x6E, 0x64, 0xA3, 0x31, 0x0A, 0x30
};
size_t packed_size = sizeof(packed_data);
MsgPack::map_t<String, String> rx_map;
MsgPack::Unpacker unpacker;
unpacker.feed(packed_data, packed_size);
unpacker.deserialize(rx_map);
if (rx_map.find("redirect") != rx_map.end()) {
Serial.println("Redirect URL: " + rx_map["redirect"]);
} else {
Serial.println("Key 'redirect' not found!");
}
Binary 또는 Hex코드 베이스로 동작되고 빠르다고 하니
이 MessagePack의 장점을 십분살려 결국 직접 만들게 되었다.
#include <Arduino.h>
String msgpack_unpack(const uint8_t *data, size_t &index) {
uint8_t firstByte = data[index++];
if (firstByte >= 0xA0 && firstByte <= 0xBF) {
size_t strLength = firstByte & 0x1F;
String value = "";
for (size_t j = 0; j < strLength; j++) {
value += (char)data[index++];
}
return "\"" + value + "\"";
}
else if (firstByte == 0xC3) {
return "true";
} else if (firstByte == 0xC2) {
return "false";
}
else if (firstByte <= 0x7F) {
return String(firstByte);
}
else if (firstByte == 0xCB) {
uint64_t rawData = 0;
for (int i = 0; i < 8; i++) {
rawData = (rawData << 8) | data[index++];
}
double floatValue;
memcpy(&floatValue, &rawData, sizeof(floatValue));
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.10f", floatValue);
return String(buffer);
}
else if (firstByte >= 0x90 && firstByte <= 0x9F) {
size_t arraySize = firstByte & 0x0F;
String result = "[";
for (size_t j = 0; j < arraySize; j++) {
if (j > 0) result += ", ";
result += msgpack_unpack(data, index);
}
result += "]";
return result;
}
else if (firstByte >= 0x80 && firstByte <= 0x8F) {
size_t mapSize = firstByte & 0x0F;
String result = "{";
for (size_t j = 0; j < mapSize; j++) {
String key = msgpack_unpack(data, index);
String value = msgpack_unpack(data, index);
if (j > 0) result += ", ";
result += key + ": " + value;
}
result += "}";
return result;
}
return "Error: Unsupported Type";
}
String msgpack_getdata(const uint8_t *data, String targetKey) {
size_t index = 0;
uint8_t firstByte = data[index++];
if (firstByte < 0x80 || firstByte > 0x8F) {
return "Error: Invalid MsgPack format!";
}
uint8_t mapSize = firstByte & 0x0F;
for (uint8_t i = 0; i < mapSize; i++) {
String key = msgpack_unpack(data, index);
if (key == "\"" + targetKey + "\"") {
return msgpack_unpack(data, index);
} else {
msgpack_unpack(data, index);
}
}
return "Error: Key Not Found";
}
void setup() {
Serial.begin(115200);
//다차원 배열예제
uint8_t packed_data[] = {
0x8D, 0xA6, 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0xB0, 0xEC, 0x8B, 0x9C, 0xEB, 0xA6, 0xAC, 0xEC, 0x96, 0xBC, 0x20, 0xEC, 0xB6, 0x9C, 0xEB, 0xA0, 0xA5, 0xAA, 0x69, 0x73, 0x5F, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0xC3, 0xA8, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0xA9, 0x2F, 0x77, 0x73, 0x2F, 0x74, 0x65, 0x73, 0x74, 0x31, 0xB2, 0x5F, 0x5F, 0x43, 0x4F, 0x52, 0x45, 0x5F, 0x45, 0x58, 0x45, 0x43, 0x5F, 0x54, 0x59, 0x50, 0x45, 0x5F, 0x5F, 0xA2, 0x57, 0x53, 0xAA, 0x40, 0x63, 0x6F, 0x72, 0x65, 0x5F, 0x74, 0x69, 0x6D, 0x65, 0x93, 0x92, 0xBA, 0x77, 0x65, 0x62, 0x73, 0x6F, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x72, 0x78, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0xCB, 0x3E, 0xF2, 0xC0, 0x00, 0x73, 0x88, 0xDC, 0xE0, 0x92, 0xAF, 0x68, 0x61, 0x6E, 0x64, 0x73, 0x68, 0x61, 0x6B, 0x65, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6B, 0xCB, 0x3F, 0x21, 0xF8, 0x00, 0x25, 0x6B, 0x7E, 0xBC, 0x92, 0xB1, 0x77, 0x65, 0x62, 0x73, 0x6F, 0x63, 0x6B, 0x65, 0x74, 0x20, 0x74, 0x78, 0x20, 0x73, 0x65, 0x6E, 0x64, 0xCB, 0x3F, 0x1B, 0xD0, 0x00, 0x8E, 0x0E, 0x58, 0xDB, 0xAB, 0x40, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5F, 0x74, 0x69, 0x6D, 0x65, 0x90, 0xAC, 0x40, 0x73, 0x65, 0x6E, 0x64, 0x65, 0x64, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x82, 0xA4, 0x6B, 0x65, 0x79, 0x31, 0xA6, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x31, 0xA4, 0x6B, 0x65, 0x79, 0x32, 0xA6, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x32, 0xA8, 0x74, 0x69, 0x6D, 0x65, 0x7A, 0x6F, 0x6E, 0x65, 0xAA, 0x41, 0x73, 0x69, 0x61, 0x2F, 0x53, 0x65, 0x6F, 0x75, 0x6C, 0xA7, 0x67, 0x65, 0x6F, 0x63, 0x6F, 0x64, 0x65, 0xA2, 0x4B, 0x52, 0xA7, 0x63, 0x6F, 0x6D, 0x6D, 0x61, 0x6E, 0x64, 0xA3, 0x31, 0x0A, 0x31, 0xAA, 0x40, 0x65, 0x78, 0x65, 0x63, 0x5F, 0x74, 0x69, 0x6D, 0x65, 0xCB, 0x3F, 0x65, 0x63, 0x00, 0x4C, 0xCE, 0x5C, 0x3B, 0xAC, 0x40, 0x75, 0x73, 0x65, 0x64, 0x5F, 0x6D, 0x65, 0x6D, 0x6F, 0x72, 0x79, 0xA5, 0x37, 0x2E, 0x32, 0x37, 0x4D, 0xA2, 0x21, 0x21, 0xB6, 0x54, 0x48, 0x49, 0x53, 0x20, 0x49, 0x53, 0x20, 0x44, 0x45, 0x42, 0x55, 0x47, 0x20, 0x4D, 0x4F, 0x44, 0x45, 0x20, 0x3A, 0x21, 0x21
};
/*
//1차원 배열예제
uint8_t packed_data[] = {
0x87, 0xA6, 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0xB0, 0xEC, 0x8B, 0x9C, 0xEB, 0xA6, 0xAC, 0xEC, 0x96, 0xBC, 0x20, 0xEC, 0xB6, 0x9C, 0xEB, 0xA0, 0xA5, 0xAA, 0x69, 0x73, 0x5F, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0xC3, 0xA8, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0xA9, 0x2F, 0x77, 0x73, 0x2F, 0x74, 0x65, 0x73, 0x74, 0x31, 0xB2, 0x5F, 0x5F, 0x43, 0x4F, 0x52, 0x45, 0x5F, 0x45, 0x58, 0x45, 0x43, 0x5F, 0x54, 0x59, 0x50, 0x45, 0x5F, 0x5F, 0xA2, 0x57, 0x53, 0xA8, 0x74, 0x69, 0x6D, 0x65, 0x7A, 0x6F, 0x6E, 0x65, 0xAA, 0x41, 0x73, 0x69, 0x61, 0x2F, 0x53, 0x65, 0x6F, 0x75, 0x6C, 0xA7, 0x67, 0x65, 0x6F, 0x63, 0x6F, 0x64, 0x65, 0xA2, 0x4B, 0x52, 0xA7, 0x63, 0x6F, 0x6D, 0x6D, 0x61, 0x6E, 0x64, 0xA3, 0x31, 0x0A, 0x30
};
*/
Serial.print("is_success: ");
Serial.println(msgpack_getdata(packed_data, "is_success"));
Serial.print("output: ");
Serial.println(msgpack_getdata(packed_data, "output"));
Serial.print("redirect: ");
Serial.println(msgpack_getdata(packed_data, "redirect"));
Serial.print("__CORE_EXEC_TYPE__: ");
Serial.println(msgpack_getdata(packed_data, "__CORE_EXEC_TYPE__"));
Serial.print("@core_time: ");
Serial.println(msgpack_getdata(packed_data, "@core_time"));
Serial.print("@query_time: ");
Serial.println(msgpack_getdata(packed_data, "@query_time"));
Serial.print("@sended_data: ");
Serial.println(msgpack_getdata(packed_data, "@sended_data"));
Serial.print("timezone: ");
Serial.println(msgpack_getdata(packed_data, "timezone"));
Serial.print("geocode: ");
Serial.println(msgpack_getdata(packed_data, "geocode"));
Serial.print("command: ");
Serial.println(msgpack_getdata(packed_data, "command"));
Serial.print("@exec_time: ");
Serial.println(msgpack_getdata(packed_data, "@exec_time"));
Serial.print("@used_memory: ");
Serial.println(msgpack_getdata(packed_data, "@used_memory"));
Serial.print("!!: ");
Serial.println(msgpack_getdata(packed_data, "!!"));
}
void loop() {
}
unpack을 하는 방법도 심플하게 구성했다.
단순히 msgpack_getdata("uint8_t의 MsgPack HEX코드", "보고 싶은 키"); 를 실행하면 Value값을 반환해준다.
//원래의 배열형태
{
"output": "시리얼 출력",
"is_success": true,
"redirect": "/ws/test1",
"__CORE_EXEC_TYPE__": "WS",
"@core_time": [
[
"websocket message rx start",
0.0000178814
],
[
"handshake check",
0.0001370907
],
[
"websocket tx send",
0.0001060963
]
],
"@query_time": [],
"@sended_data": {
"key1": "value1",
"key2": "value2"
},
"timezone": "Asia/Seoul",
"geocode": "KR",
"command": "1\n1",
"@exec_time": 0.0002610684,
"@used_memory": "7.27M",
"!!": "THIS IS DEBUG MODE :!!"
}
이렇게 자체적으로 개발한 함수에도 한가지 이슈가 있는데,
다차원 배열의 경우, 단일 String key가 아니라서 상위 Key를 함수에 넣으면 하위 배열을 JSON형태로 반환하도록 하였다.
이 경우, 이미 Arduino의 JSON라이브러리가 아주 잘 되어 있음으로 이를 활용하여 쉽게 해결이 가능하다 :)
이 포스팅을 통해 Arduino뿐만 아니라 C Language처럼 저 수준의 언어에서 MessagePack을 사용할때
나처럼 골머리 싸매고 있지 않길....
👇👇 시뮬레이션을 직접 구동할 수 있도록 원본 코드도 함께 공유한다. 👇👇
VScode + Platform.IO + Wokwi Simulator (ESP32)
'IT' 카테고리의 다른 글
검열하는 중궈판 openAI - DeepSeek (0) | 2025.01.27 |
---|---|
똥서버에 웹소켓 100개 연결하기 (php openswoole benchmark) (0) | 2024.12.17 |
macOS에서 윈도우 키보드의 커맨드키를 설정하는 방법 (0) | 2024.12.16 |
애플 M1 Max 맥북과 제온 40코어 128GB 서버 간 php 벤치마크 비교 (1) | 2024.11.03 |
구름 IDE에서 저장 시 자동으로 Github에 커밋하는 방법 (공유된 사용자마다 개별계정 연결 방법 포함) (1) | 2024.10.29 |