Protocol

BLE 연결

G큐브는 Nordic UART Service(NUS)를 광고합니다. 클라이언트는 이 서비스에 연결한 뒤 TX 특성으로 명령을 쓰고, RX 특성의 Notify로 응답을 받습니다.

GATT UUID

구분UUID속성방향
Service (NUS)6e400001-b5a3-f393-e0a9-e50e24dcca9e
TX Characteristic6e400002-b5a3-f393-e0a9-e50e24dcca9eWrite / WriteWithoutResponse클라이언트→큐브
RX Characteristic6e400003-b5a3-f393-e0a9-e50e24dcca9eNotify큐브→클라이언트

주의: NUS의 명명 관습(TX=큐브가 데이터를 보내는 채널)과 반대입니다. 여기서 TX(...0002)는 클라이언트가 명령을 쓰는 특성, RX(...0003)는 클라이언트가 알림을 받는 특성입니다. 소스 코드 기준으로 표기했습니다.

기기 탐색(스캔)

광고 이름은 PINGPONG. 접두사로 시작합니다. 그룹ID를 사용하면 PINGPONG.<groupID> 형태가 됩니다.

// 스캔 필터 예 (Web Bluetooth / Scratch)
filters: [{ namePrefix: "PINGPONG." }]
// 서비스 UUID로도 필터 가능
optionalServices: ["6e400001-b5a3-f393-e0a9-e50e24dcca9e"]

연결 후 표준 시퀀스

펌웨어/클라이언트가 실제로 사용하는 초기화 흐름입니다.

  1. NUS 서비스 연결 → RX 특성 Notify 구독 시작
  2. 연결 안정화 대기(약 500ms)
  3. 단일 큐브면 getOrangeRedForSoundData(연결표시 LED), 다중 큐브면 SetMultiroleInAction 전송
  4. 연결 완료 알림 수신: 단일 큐브 0xCE(14B), 다중 큐브 0xAE(18B)
  5. 펌웨어 버전 조회 getProductVersion → 필요 시 센서 구독 getSensorsData (연결 후 약 3초 뒤 전송 권장)
센서 구독(0xB8)은 연결 직후 곧바로 보내지 말고 IMU 안정화를 위해 약 3000ms 후 전송하세요. (소스 fullConnect() 기준)

펌웨어의 수신 패킷 검증 조건

ble_sync_multirole.c의 NUS 수신 핸들러는 아래 조건을 만족해야 유효 패킷으로 처리합니다.

  • 누적 수신 길이 > 5바이트
  • Data[0] == 0xFF && Data[1] == 0xFF
  • Data[6](OpCode)가 등록된 Command Reference 목록에 존재
  • Data[7:8]의 패킷 크기만큼 수신되면 처리 시작

패킷 구조

모든 명령은 공통 헤더로 시작합니다. 명령 종류에 따라 Generic 헤더(스텝모터·집계 계열, 13바이트 헤더)와 Task 헤더(LED·서보·음악·조회 계열)로 나뉩니다. 두 헤더 모두 [0:8] 구조는 동일합니다.

공통 앞부분 [0:8] (9바이트)

FF[0]
FF[1]
FF[2]
ID[3] CubeID
AS[4] Assigned Hi
AS[5] Assigned Lo
OP[6] OpCode
SZ[7] Size Hi
SZ[8] Size Lo
바이트이름설명
[0:2]가상 큐브 ID 접두대부분 FF FF FF. 컬러LED 등 일부 태스크는 FF FF 00. 동글은 DD DD DD.
[3]Cube IDFF=전체(All), 0x00~0x07=빌딩 순서(연결 순번), AA=집계기(Star/Aggregator)
[4:5]Assigned IDGeneric: [4]=큐브수<<4. Task: Task ID (예 00 A1, 00 C8).
[6]OpCode명령 코드. 「OPCODE 목록」 탭 참조.
[7:8]Packet Size전체 패킷 바이트 수(빅엔디언). = 13 + 값바이트수(Generic) 또는 명령별 고정.

Generic 헤더 [9:12]

스텝모터·집계 계열(C1 CC CA CB CD).

MD[9] Mode
MT[10] Method
ST[11] StepType
PS[12] PauseState
[9] Mode1=Peripheral, 2=Multirole, 3=CRCCheck
[10] Method0=Continuous, 1=RelativeSingle, 2=AbsoluteSingle, 3=ScheduledSteps, 4=ScheduledPoints
[11] StepType0=FullSteps, 4=SetServo
[12] PauseState1=Pause, 2=Resume

값(속도·스텝 등)은 [13]부터 이어집니다.

Task 헤더 [9:10]

서보·LED·음악·조회 계열(E1 CE CF C7 E8 A2 B8 BA B1).

PR[9] Property
PR[10] Property2

[9](및 [10])에 명령별 Property가 들어가고, 값은 그 뒤에 이어집니다. 명령별 정확한 오프셋은 각 탭의 바이트맵을 참조하세요.

예) 서보 E1: [9]=Mode(1), [11]=각도, [12]=타임아웃

예시로 이해하기 — 컬러LED 켜기

1번 큐브(연결 순번 0)를 빨강(R=255,G=0,B=0), 밝기 80으로 설정:

FF[0]
FF[1]
00[2]
00[3] cube
00[4]
00[5]
CE[6] op
00[7]
0E[8] size=14
01[9] RGB
FF[10] R
00[11] G
00[12] B
50[13] 밝기
FF FF 00 00 00 00 CE 00 0E 01 FF 00 00 50   // 14 bytes

OPCODE 전체 목록

G큐브 펌웨어에서 실제 처리되는 명령을 정리한 표입니다. 활성은 현재 펌웨어에서 정상 처리되는 명령입니다.

동작 명령 (Set)

OpCode이름기능상태
0xC1SET_SINGLESTEPS단일 스텝 동작(속도+스텝수, 1회)활성
0xCCSET_CONTINUOUSSTEPS연속 회전(속도 기반 시작/정지)활성
0xCASET_SCHEDULEDSTEPS스케줄 스텝 데이터셋 전송/재생활성
0xCBSET_SCHEDULEDPOINTS스케줄 포인트 재생활성
0xCDSET_AGGREGATESTEPS여러 큐브 스텝 묶음 전송(집계)활성
0xC0SET_PAUSESTEPS일시정지/재개활성
0xC6SET_INSTANTTORQUE토크 값 설정활성
0xC2SET_MULTITASK_TIMERRTC 타이머 기반 멀티태스크활성
0xE1SET_SINGLESERVO서보 각도 설정활성
0xCESET_COLORLED내장 컬러LED (HSV/RGB/인덱스)활성
0xCFSET_DIGITALPORT확장포트 Dout HIGH/LOW활성
0xC7SET_WS2812BWS2812(네오픽셀) LED활성
0xC9SET_POWERSTATE전원상태(리부트/전원오프)활성
0xE8SET_MUSICNOTE_INACTION음표 재생/집계 음악활성
0xEFSET_GENERATETONE버저 톤 생성활성
0xE2SET_MULTIROLE_UNPLUGGED언플러그드(자율) 동작활성

조회 명령 (Get) · 응답은 RX Notify

OpCode이름기능
0xB1GET_UPDATE / PRODUCTINFO제품 정보·펌웨어 버전 (Property B1=상세, B2=버전)
0xBAGET_PERIPHERALS버튼·컬러LED·근접·IMU·배터리 조회 (Property로 세부 선택)
0xB8GET_SENSORS센서 통합 스트림(버튼/자이로/가속도/근접/확장)

집계기(Aggregator/멀티롤)

OpCode이름기능
0xAASET_MULTIROLE_AGGREGATOR집계기에 연결 큐브 수·가상색 설정
0xADSET_MULTIROLE_INACTION멀티롤 시작(연결/그룹/모드 지정)
0xAESET_MULTIROLE_VIRTUALCUBECOLOR가상 색 순서 지정 / 연결완료 알림
0xA0SET_MULTIROLE_CLEARVIRTUALCUBECOLOR가상 색/연결상태 초기화
0xABGET_MULTIROLE_VIRTUALCUBEID가상 큐브 ID 조회
0xACGET_MULTIROLE_VIRTUALCUBECOLOR가상 색 배열 조회
0xA8SET_MULTIROLE_REBOOTAGGREGATOR집계기 리부트
0xAFCLASSIC_MULTIROLE_SETTINGS클래식 멀티롤 설정

아두이노·확장·플래시·기타

OpCode이름기능
0xA2ARDUINO_I2CI2C 장치·LED매트릭스(주소 0x70) 제어
0xA3SET_CUBEINO_COMMAND큐브이노/아두이노/마이크로비트 제어
0xA4SET_LINEBOARD_COMMAND라인봇(트레이서보드) 제어
0xF9FLASH_DISCOVERYGROUPID그룹ID 탐색(임시 플래시 기록)
0xFAFLASH_DISCOVERYFINISHGROUPID그룹ID 확정(SetGroupID)
0xFDTEST / FACTORY공장/고객서비스 페이지
0xDADONGLE_INACTION전용 동글 제어(연결/해제/버전)
주석 처리(비활성)된 OpCode: 0xC8(SetOrigin), 0xB0(GetOrigin), 0xB4(GetBattery 단독), 0xBB(GetProximity), 0xBE(GetColorLED), 리모컨 계열 0x0A~0x10 등. 필요 기능은 위 활성 명령으로 대체됩니다(예: 배터리는 0xBA+Property B4).

스테퍼 모터 · 서보

G큐브의 핵심 구동부. 속도는 SPS(Steps Per Second) 또는 RPM으로 지정합니다. 속도 부호가 방향입니다.

속도 환산: 사용자 속도 ±1~100 → SPS ±100~1000. 단일스텝: SPS = 속도×9 + 100. 연속회전: SPS = (1100×속도 − 10000) / 속도. 스텝수 최대 32768. 1도 ≈ 5.5스텝.

SET SetSingleSteps 0xC1 — 19바이트

지정한 스텝수만큼 1회 이동 후 정지.

FF FF FF[0:2]
ID[3]
N<<4[4]
00[5]
C1[6]
00 13[7:8]=19
02[9] Mode
01[10] Rel
00[11] Full
01[12] Pause
SPS[13:14]
00 00[15:16] Phase
STEP[17:18]
필드
[13:14] StepRateSPS(빅엔디언, 음수는 65536+n). 정지=0
[15:16] StartPhase초기 위상(스텝), 보통 0
[17:18] StepValue이동 스텝수 0~65535(Relative) / 0~1000(Absolute)
// 예시) 0번 큐브를 속도 50(정회전)으로 100스텝(약 18°) 이동
//        SPS = 50×9 + 100 = 550 = 0x0226,  스텝 100 = 0x0064
FF FF FF 00 10 00 C1 00 13 02 01 00 01 02 26 00 00 00 64

SET SetContinuousSteps 0xCC — 15바이트

속도 기반 연속 회전. 속도 0이면 정지(PauseState=Pause), 그 외에는 Resume.

FF FF FF[0:2]
ID[3]
N<<4[4]
00[5]
CC[6]
00 0F[7:8]=15
02[9] Mode
00[10] Cont
00[11] Full
01/02[12] Pause/Resume
SPS[13:14]
// 예시) 0번 큐브 속도 50으로 계속 회전  →  SPS = (1100×50 − 10000)/50 = 900 = 0x0384
FF FF FF 00 10 00 CC 00 0F 02 00 00 02 03 84
// 정지 (속도 0, PauseState=Pause)
FF FF FF 00 10 00 CC 00 0F 02 00 00 01 00 00

SET SetScheduledSteps 0xCA — (15 + 값)바이트, CRC16

여러 (속도,스텝) 데이터셋을 미리 전송해두고 재생. Generic 헤더의 Mode=2, Method=3(ScheduledSteps), StepType=0(Full) 또는 4(SetServo).

바이트필드
[13:14]CRC16 (값 구간 [15:끝]에 대해 계산, CCITT 0x1021)
[15:16]StepRate #1 (SPS)
[17:18]Steps #1
[19],[20](SetServo일 때) ServoAngle, ServoTimeout
데이터셋 반복
전송은 Pause 상태로 보내고, 응답(현재 데이터셋 인덱스)을 받은 뒤 0xC0 Resume으로 재생을 시작하는 것이 표준 흐름입니다.

SET SetScheduledPoints 0xCB — (15 + 값)바이트

이미 전송된 스케줄 데이터셋에서 특정 포인트 구간을 반복 재생. 값: StartPoint[17:18]:StopPoint[19:20]:Repeats[21] 반복.

SET SetAggregateSteps 0xCD

여러 큐브의 SingleSteps/ContinuousSteps/Scheduled 데이터를 하나로 묶어 집계기(Star, ID=AA)에 전송. 헤더 크기 = 13 + Σ(각 큐브 패킷), Assigned ID 상위 = 큐브수<<4, Method는 묶는 명령 종류(0/1/2/3/4).

FF FF FF AA [N<<4] [00] CD [size_hi] [size_lo] 02 [method] 00 00
  { 큐브0 패킷 } { 큐브1 패킷 } ...

20바이트를 넘는 명령 (패킷 조각)

BLE 1패킷은 20바이트(MTU 23 − 3)이므로 0xCA·0xCB·0xCD 처럼 긴 명령은 여러 조각으로 나뉘어 전달됩니다. 예를 들어 35바이트 SetScheduledPoints는 20 + 15 두 조각입니다. 호스트는 조각을 나누어 보내기만 하면 되고, 재조립은 펌웨어가 합니다 — 조각 사이에 지연을 넣거나 응답을 기다릴 필요는 없습니다.

구버전 주의 — 조각으로 나뉘는 명령이 하위 큐브까지 도달하지 못하던 문제가 두 차례 있었습니다.
  • 큐브가 앞 조각을 처리하는 동안 뒤 조각이 폐기됨 → 수신 스테이징 버퍼로 해결 (2026-07-22)
  • 집계기 → 하위 큐브 전송 큐(GATTC)가 깊이 1이라 두 번째 조각을 실을 수 없어, 집계기만 움직이고 하위 큐브는 스케줄 명령을 통째로 놓침 → 큐 깊이 6으로 해결 (2026-08-11)
두 경우 모두 20바이트 이하 명령(컬러LED·SingleSteps·ContinuousSteps)은 정상이라 증상이 잘 드러나지 않았습니다. 여러 큐브에 다중 포인트 동작을 보내는 앱은 2026-08-11 이후 빌드의 펌웨어를 쓰세요.

SET SetPauseSteps 0xC0 — 12바이트

[9] PauseState: 1=Pause, 2=Resume, 3=Radio. [10:11] SyncClock(StartTime). Radio 설정 시 집계기 ID 0xFFFFFFAA 사용.

SET SetInstantTorque 0xC6 — 10바이트

[9]: 0=기본(최소) 토크, 1=최대 토크.

서보 (Servo)

SET SetSingleServo 0xE1 — 13바이트

확장 서보 모터의 각도를 설정합니다.

FF FF FF[0:2]
ID[3] cube
00 00[4:5]
E1[6]
00 0D[7:8]=13
01[9] Mode
00[10]
ANG[11] 각도
TO[12] 타임아웃
필드
[9] Mode1=Peripheral, 2=Multirole
[11] ServoAngle0~180도
[12] ServoTimeout전원유지 시간(초) 1~255. 0=각도만 걸고 유지 안 함, 0xFF=약 21.8분
// 예시) 0번 큐브의 서보를 90°로 (타임아웃 0 = 각도만 지정)  →  90 = 0x5A
FF FF FF 00 00 00 E1 00 0D 01 00 5A 00
스케줄 스텝(0xCA)에서 StepType=4(SetServo)로 지정하면 스텝과 함께 서보 각도/타임아웃을 데이터셋에 포함시켜 시퀀스로 제어할 수 있습니다.

LED · 디스플레이 · 음악

SET SetColorLED 0xCE — 14바이트

큐브 내장 RGB LED. 색공간 3종 지원.

FF FF 00[0:2]
ID[3] cube
00 00[4:5]
CE[6]
00 0E[7:8]=14
CS[9] 색공간
C0[10]
C1[11]
C2[12]
V[13] 밝기
[9] 색공간[10:12][13]
0 = HSVHue(0~360, 2바이트) 등Brightness 0~100
1 = RGBR[10], G[11], B[12] (각 0~255)
2 = ColorIndex[12]에 인덱스: 0=RED,1=GREEN,2=BLUE,3=CYAN,4=MAGENTA,5=YELLOW,6=VIOLET,7=ORANGE,E=WHITE,F=BLACK
// 2번 큐브 초록, 밝기 100 (RGB)
FF FF 00 01 00 00 CE 00 0E 01 00 FF 00 64

SET SetDigitalPort 0xCF — 10바이트

확장포트 Dout 핀 HIGH/LOW. [9]: 0=LOW, 1=HIGH.

FF FF FF [cube] 00 00 CF 00 0A [onoff]

SET SetWs2812bLED 0xC7 — 13바이트

외장 네오픽셀(WS2812) 제어. [9]=LED 번호(1,2,3…), [10:12]=R,G,B.

// 예시) 0번 큐브의 1번 네오픽셀을 파랑(R=0,G=0,B=255)으로
FF FF FF 00 00 00 C7 00 0D 01 00 00 FF

SET LED 매트릭스 (I2C, 0xA2 주소 0x70)

HT16K33 기반 8×8 LED 매트릭스. Task ID로 동작을 구분합니다.

Task ID동작
00E1픽셀 켜기x, y, on/off
00E2그림(8바이트)행 8바이트 비트맵
00E3문자열 스크롤스크롤주기[10] + ASCII HEX (최대 20자)
00E4디스플레이 제어1=on, 0=off, 2=clear
00E5밝기0~15
00E6깜빡임0=off,1=0.5Hz,2=1Hz,3=2Hz

공통: [9]=I2C 주소 0x70. 스크래치 클라이언트는 픽셀 명령을 FF FF FF [cube] 00 [task] A2 00 0D 70 [y] [x] [plot] 형태로 전송합니다.

// 예시) 0번 큐브 매트릭스에서 (x=3, y=4) 픽셀 켜기  (Task 00E1)
FF FF FF 00 00 E1 A2 00 0D 70 04 03 01

음악 · 버저 (Music)

SET SetMusicNotes 0xE8 (Task 00A1) — (11 + 음표)바이트

단일 큐브 음표 재생. 음표 하나당 3바이트: PianoKey : Duration : Rest.

FF FF FF[0:2]
ID[3]
00 A1[4:5] Task
E8[6]
00 0E[7:8]
00[9] NoteId
00[10] Play
KEY[11]
DUR[12]
RST[13]
[10] PlayState0=Play, 1=Pause/Stop, 2=Resume/Play
[11] PianoKey피아노 키 번호 (아래 음계표). 특수: A1=StartRepeat, A2=StopRepeat
[12] Duration음길이 = 초×50 (예: 온음표 60BPM=0x64)
[13] Rest쉼표 길이 = 초×50
// 예시) 0번 큐브에서 도(C5) 재생  →  키 0x34, 길이 0x32(=50→1초), 쉼표 0
FF FF FF 00 00 A1 E8 00 0E 00 00 34 32 00
음 재생 시 도트매트릭스 표시는 FW 1.0.4.85부터 기본 비활성화입니다(2026-08-08). 84 이하에서는 재생 중인 음 하나하나가 확장포트의 8×8 매트릭스(HT16K33)에 막대그래프로 표시됐지만, "음과 표시가 어긋나 보인다"는 사용자 의견에 따라 껐습니다. 재생음 자체와 명령 형식은 그대로입니다. 매트릭스에 직접 보낸 명령(0xA2 주소 0x70)은 영향이 없으므로, 멜로디에 맞춘 표시가 필요하면 앱에서 매트릭스 명령을 함께 보내세요.

피아노 키 · 음계표 (발췌)

키(hex)음계주파수(Hz)
40Do (높은도)C61046.4
34DoC5 (Tenor C)523.2
33TiB4493.9
31LaA4440.0
28DoC4 (Middle C)261.6
25LaA3220.0

평균율 배율 1.05946. 스크래치 클라이언트는 PianoKey = 음번호 − 8 보정 후 전송합니다.

SET SetAggregateMusicNotes 0xE8 (Task 00A2)

여러 큐브 화음. 집계기 ID AA, Assigned ID 상위 = 큐브수<<4. 각 큐브의 음표 패킷을 이어붙입니다.

SET GenerateBuzzerTone 0xEF — 13바이트

[9] SoundFormatE=PianoKey, C=WholeNote
[10:12]NoteIndex(WholeNote 0~16 또는 PianoKey 37~64) : Amplitude(1~10=10~100%) : Duration(1~40)
// 예시) 0번 큐브 버저로 WholeNote C4(index 2)를 세기 5, 길이 10으로
FF FF FF 00 00 00 EF 00 0D 0C 02 05 0A

큐브 연결 · 센서 읽기

멀티롤 · 집계 (여러 큐브 연결)

여러 큐브를 하나의 Star(집계기)에 연결해 로봇을 구성하는 방식입니다. Star의 큐브 ID는 0xAA, 브로드캐스트는 0xFF입니다.

SET SetMultiroleInAction 0xAD — 11바이트

멀티롤 연결/모드를 시작합니다. 스크래치 클라이언트가 다중 큐브 연결 시 사용.

FF FF FF AA [N<<4] 00 AD 00 0B 0A 00        // 기본
FF FF FF AA [N<<4] 00 AD 00 0B 1A [groupId]  // 그룹ID 사용 시
[9] InAction0A=그룹ID 비활성, 1A=그룹ID 활성, 0B=예약
[10] Property그룹번호 1바이트(0x00~0x99, 각 자리 0~9). 아래 그룹번호 절 참조. Web Bluetooth 모드는 03.

SET SetMultiroleAggregator 0xAA

집계기에 연결할 큐브 수(ConnectionLinkCount, 최대 7 또는 0=전체)와 자동 가상색을 설정. [9]=큐브수, [10]=가상색 자동(0A/1A/00), 이후 각 큐브 ID.

RX 연결 상태 알림

OpCode의미길이
0xCE단일 큐브 연결 완료14B
0xADn개 연결 중 / 하위 큐브 끊김18B, [9]=현재 연결 인덱스
0xAEn개 모두 연결 완료18B, [9]=큐브수−1
하위 큐브 응답 중계 85+ — 집계기는 하위 큐브가 올려 보낸 0xBA(GetPeripherals) · 0xE8(음악) · 0xB1(제품정보·펌웨어 버전) 응답을 그대로 호스트로 전달합니다. 따라서 집계기뿐 아니라 각 하위 큐브의 배터리·펌웨어 버전을 개별 조회할 수 있습니다. 이 세 OpCode 외의 응답은 집계기 내부에서 처리되고 올라오지 않습니다.

가상색 인덱스

연결 순서를 색으로 표현: 0=RED, 1=GREEN, 2=BLUE, 3=CYAN, 4=MAGENTA, 5=YELLOW, 6=SPRINGGREEN/VIOLET, 7=ORANGERED, E=WHITE(미정), F=BLACK(끊김).

SET RebootMultiroleAggregator 0xA8 — 10바이트

FF FF FF FF 00 00 A8 00 0A 01

연결 해제 시 클라이언트는 이 리부트 명령을 보낸 뒤 소켓을 닫습니다.

그룹번호 (Discovery Group ID)

여러 대의 G큐브가 같은 공간에 있을 때 서로 다른 로봇끼리 섞여 연결되는 것을 막는 식별자입니다. 집계기는 자신과 같은 그룹번호를 가진 큐브만 멤버로 받아들입니다. 값은 플래시에 저장되어 전원을 꺼도 유지됩니다.

범위와 표기

그룹번호는 0x00 ~ 0x99 범위의 16진수 1바이트입니다. 상위 4비트가 앞자리, 하위 4비트가 뒷자리이며, 각 자리는 0~9만 사용하고 A~F는 쓰지 않습니다.

groupId = (앞자리 << 4) | 뒷자리        // 각 자리 0~9

  0x00   0x07   0x12   0x25   0x42   0x99

16진수로 읽은 두 글자가 곧 사용자가 보는 그룹번호입니다 — 0x12는 "일이", 0x25는 "이오"로 읽습니다. 십진수를 그대로 넣으면 안 됩니다. 예를 들어 그룹번호 12를 십진수 12(0x0C)로 보내면 앞자리 0, 뒷자리 0xC가 되어 뒷자리 색이 팔레트의 예약(소등) 영역으로 빠집니다.

0x00은 "미사용"을 뜻해, 이 경우 큐브는 그룹 제한 없이 동작하고 컬러 LED도 무지개(Hue) 표시로 돌아갑니다.

각 자리가 0~9로 제한되므로 실제로 쓸 수 있는 조합은 100가지(0x00~0x99 중 유효값)입니다. 0x0A~0x0F, 0xA0 이상처럼 A~F가 섞인 값은 펌웨어가 막지는 않지만 대응하는 음·색이 정의되어 있지 않으므로 사용하지 마세요.

큐브가 그룹번호를 알려주는 방법

그룹번호가 설정되면 큐브는 소리와 색 두 가지로 자기 번호를 표현합니다. 두 방식 모두 자릿수를 앞자리 → 뒷자리 순으로 나타냅니다.

① 음계 — 자리 숫자마다 음 하나

숫자를 도레미에 그대로 대응시켰습니다. 0이 도(C4)이고 숫자가 하나 커질 때마다 음이 하나씩 올라갑니다. 앞자리를 0.2초 짧게, 뒷자리를 0.6초 길게 울려 둘을 구분합니다.

숫자계이름음이름주파수내부 WholeNote 인덱스
0C4262 Hz2
1D4294 Hz3
2E4330 Hz4
3F4349 Hz5
4G4392 Hz6
5A4440 Hz7
6B4494 Hz8
7도(높은)C5523 Hz9
8레(높은)D5587 Hz10
9미(높은)E5659 Hz11

내부 인덱스 = 숫자 + 2 입니다. WholeNote 0·1은 A3(라)·B3(시)라서 도(C4)가 인덱스 2부터 시작합니다.

예) 그룹번호 0x25(짧게) → (길게)  ·  그룹번호 0x07(짧게) → 높은 도(길게)

② 색 — 자리 숫자마다 색 하나

컬러 LED가 0.5초 간격 5단계를 반복하며 두 자리를 보여 줍니다. 뒷자리를 두 틱(1.0초) 켜서 앞자리와 구분합니다.

[앞자리 색] → [소등] → [뒷자리 색] → [뒷자리 색] → [소등]  (총 2.5초 주기)
숫자RGB 비율 (R, G, B)
0빨강 RED0.4, 0, 0
1초록 GREEN0, 0.4, 0
2파랑 BLUE0, 0, 0.5
3청록 CYAN0, 0.4, 0.4
4자홍 MAGENTA0.4, 0, 0.4
5노랑 YELLOW0.4, 0.4, 0
6보라 VIOLET0.25, 0, 0.85
7주황 ORANGE0.85, 0.15, 0
8어두운 빨강 DARK RED0.1, 0, 0
9어두운 초록 DARK GREEN0, 0.1, 0
8·9는 0·1과 같은 색상을 어둡게 표현한 것입니다(빨강/초록 계열). 밝기로 구분하므로 밝은 조명 아래에서는 헷갈릴 수 있습니다. 8·9가 들어간 번호는 소리로 확인하는 편이 확실합니다. 이 두 색의 밝기는 2025-12-18에 0.2에서 0.1로 낮췄습니다.
멀티롤 가상색 인덱스(연결 순번을 나타내는 위의 0=RED … 7=ORANGERED)와 숫자→색 규칙이 같습니다. 다만 용도가 다릅니다 — 가상색은 "몇 번째로 연결된 큐브인가", 그룹번호 색은 "어느 그룹인가"를 뜻합니다.

표시가 나오는 시점

전원을 켤 때, 그룹번호를 새로 설정할 때, 그리고 연결이 끊어져 대기 상태로 돌아갈 때 소리가 납니다. 색 표시는 그룹번호가 설정되어 있는 동안 계속 반복됩니다(SET_DISCOVERYID 모드). 확장포트에 I2C 장치가 꽂혀 있으면 컬러 LED 자동표시가 멈추므로 그룹번호 색도 보이지 않습니다.

BLE 기기 이름

큐브는 광고 이름 뒤에 그룹번호를 16진수 두 자리로 붙입니다 — 0x25PINGPONG.25. 펌웨어가 %01X%01X 로 찍기 때문에 저장된 바이트를 16진수로 읽은 값이 그대로 이름이 됩니다. 앱이 스캔 목록에서 그룹을 구분하는 데 쓸 수 있습니다. 0x00이면 PINGPONG.00이 됩니다.

SET SetGroupID 0xFA (FlashDiscoveryFinishGroupId) — 11바이트

FF FF FF FF 00 00 FA 00 0B FE [groupId]

// 예) 그룹번호 0x25 로 설정
FF FF FF FF 00 00 FA 00 0B FE 25
[9] InActionFE=쓰기, FA=읽기, FC=지움(그룹번호 해제)
[10] groupId0x00~0x99 (각 자리 0~9). 읽기 요청 시 응답 [10]에 현재 값이 실림
그룹번호 0x90~0x99는 특수 동작이 있습니다. 앞자리가 9이면 5V 충전기를 연결해도 큐브가 자동으로 꺼지지 않습니다(FW 1.0.4.85, 2025-12-18). 전시·시연처럼 상시 급전 상태로 운용할 때 쓰라고 만든 예외입니다. 일반 사용에는 0x00~0x89를 쓰세요.

예시 : 큐브 2개 연결 (집계기 Star + 멤버)

호스트(PC·앱)는 Star(집계기) 큐브 1개에만 BLE로 연결합니다. 그 뒤 0xAD 멀티롤 시작 명령을 보내면 Star가 자신의 무선으로 멤버 큐브를 탐색·연결합니다. 이때 멤버 큐브의 전원 버튼을 짧게 눌러 Star가 발견하도록 합니다.

연결 시퀀스 (2개 큐브 = Star 0번 + 멤버 1번)

단계동작 / 바이트
이름 PINGPONG.으로 스캔 → Star에 GATT 연결 → RX 특성 Notify 구독
약 500ms 후 멀티롤 시작(N=2) 전송 → FF FF FF AA 20 00 AD 00 0B 0A 00 (20 = 2<<4)
멤버 큐브 전원 버튼을 짧게 눌러 연결 유도
진행 알림 수신 0xAD ([9]=현재 인덱스) → 완료 알림 0xAE, [9]==1(=N−1)이면 2개 모두 연결
각 큐브 개별 제어([3]=0 Star, 1 멤버) 또는 집계기([3]=AA)로 일괄 제어. 센서는 연결 3초 뒤 0xB8 구독

Web Bluetooth 예제 (Chrome)

const SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const TX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
const RX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
const CUBE_COUNT = 2;               // Star(0) + 멤버(1)
let txChar;

const cmd = {
  // 멀티롤 시작: N개 큐브 연결 (Star 포함).  [4] = N<<4
  multiroleStart: (n) => new Uint8Array([0xFF,0xFF,0xFF,0xAA, n<<4, 0x00, 0xAD, 0x00,0x0B, 0x0A, 0x00]),
  // 컬러 LED (cube=0 Star, 1 멤버)
  colorLED: (cube,r,g,b,br=80) => new Uint8Array([0xFF,0xFF,0x00,cube,0x00,0x00,0xCE,0x00,0x0E,0x01,r,g,b,br]),
  // 전체 큐브 센서 구독 (pos=0xFF)
  getSensors: (pos=0xFF,intv=10) => new Uint8Array([0xFF,0xFF,0xFF,pos,0x00,0xC8,0xB8,0x00,0x0B,intv,0x01]),
};

const send = (b) => txChar.writeValueWithoutResponse(b);

async function connectTwoCubes() {
  // ① Star 한 대에만 BLE 연결
  const device = await navigator.bluetooth.requestDevice({
    filters: [{ namePrefix: "PINGPONG." }], optionalServices: [SERVICE]
  });
  const server = await device.gatt.connect();
  const svc = await server.getPrimaryService(SERVICE);
  txChar = await svc.getCharacteristic(TX);
  const rx = await svc.getCharacteristic(RX);
  await rx.startNotifications();

  // ④ 연결 상태 알림 수신
  rx.addEventListener('characteristicvaluechanged', e => {
    const v = new Uint8Array(e.target.value.buffer);
    if (v[6] === 0xAD) console.log(`연결 진행 중 · 인덱스 ${v[9]}`);
    if (v[6] === 0xAE && v[9] === CUBE_COUNT - 1) {
      console.log("2개 큐브 연결 완료!");
      onAllConnected();
    }
  });

  // ② 500ms 후 멀티롤 시작 → Star가 멤버를 탐색
  setTimeout(() => send(cmd.multiroleStart(CUBE_COUNT)), 500);
  // ③ 이제 멤버 큐브의 전원 버튼을 짧게 눌러 주세요.
}

async function onAllConnected() {
  // ⑤ 개별 제어: 0번(Star) 빨강, 1번(멤버) 초록
  await send(cmd.colorLED(0, 255, 0, 0));
  await send(cmd.colorLED(1, 0, 255, 0));
  // 두 큐브 센서 구독 (연결 3초 뒤 권장)
  setTimeout(() => send(cmd.getSensors(0xFF, 10)), 3000);
}

전송 바이트 확인 — 멀티롤 시작(N=2): FF FF FF AA 20 00 AD 00 0B 0A 00 · 0번 빨강: FF FF 00 00 00 00 CE 00 0E 01 FF 00 00 50 · 1번 초록: FF FF 00 01 00 00 CE 00 0E 01 00 FF 00 50

3개 이상도 동일합니다. CUBE_COUNT만 바꾸면 [4]=N<<4와 완료 조건 [9]==N−1이 자동으로 맞춰집니다. Python·Node.js·Unity도 같은 순서로, 같은 바이트를 보내면 됩니다.

센서 읽기

SET GetSensors 0xB8 — 11바이트 (구독 시작)

버튼·자이로·가속도(기울기)·근접·확장포트 값을 스트림으로 받습니다.

FF FF FF[0:2]
POS[3] 큐브
00 C8[4:5] Task
B8[6]
00 0B[7:8]=11
INT[9] 주기
MT[10] Method
[3] Position0xFF=전체, 0x00~=특정 큐브
[9] InAction0=단발, 10~100=연속. FW 1.0.4.85부터 실효 주기가 절반(InAction × 0.005초 = 0.05~0.5초). 84 이하는 × 0.01초
[10] Method1=실수 8bit(20B), 2=ADC 12bit, 3/4=실수 12bit, 7=+큐브이노 아날로그
연결 직후 약 3초 뒤에 전송하세요. 스크래치 클라이언트는 getSensorsData(0xFF, 10)(전체·주기 10)을 사용합니다. 85 이상에서 84와 같은 주기를 원하면 InAction 값을 2배로 보내세요.

여러 큐브를 연결했을 때의 실효 출력 주기

집계기는 하위 큐브들의 센서 패킷을 모아 한 프레임으로 올려 보냅니다. 하위 큐브는 각자의 타이머로 보내고 집계기도 자기 타이머로 깨어나므로, 둘의 위상이 어긋나면 그 주기에 아직 도착하지 않은 큐브가 생깁니다. FW 1.0.4.85는 한 주기만 놓쳐도 프레임을 강제로 내보냅니다(2026-08-08, 이전 임계 3주기).

InAction설정 주기이론 출력큐브 3대 실측 85
100.05초20.35 Hz18.5 ~ 20.4 Hz (명령 부하 4~64회/초)
값이 한 주기 늦을 수 있습니다. 어떤 큐브의 패킷이 강제 전송 시점까지 도착하지 않으면 그 큐브 자리만 직전값이 실려 나갑니다 — 갱신 지연과 출력 주기를 맞바꾼 결과입니다. 반대로 모든 큐브가 미도착인 완전 공백 주기는 아예 보내지 않으므로, 앱에서 "값이 통째로 고정"되어 보이는 현상은 없습니다. 변화를 놓치면 안 되는 용도라면 InAction 을 키워(주기를 늘려) 위상 어긋남 자체를 줄이세요.

RX 센서 응답 (Method 1, 20바이트)

OpCode [6]=0xB8. 실제 클라이언트가 파싱하는 오프셋:

바이트의미변환
[0]CubeId 최상위 바이트 (큐브 식별)0xA0 | 가상색인덱스. 상위 니블은 항상 0xA 고정, 하위 니블 [0] & 0x0F큐브 번호(연결 순번 0~7). 집계기(Star)는 0
[1]배터리 잔량 85+0~100 (%)
[2]펌웨어 빌드 85+예: 85
[3]큐브 번호84와 동일. 단일 연결 시 0x07 → 0으로 매핑. [0] & 0x0F 와 같은 값
[5]구분자 85+고정 0x77. 84 이하에서는 TaskId(연속 스트림에서 항상 0xC8)
[11]Button0=해제,1=눌림,2=Latched(눌렀다 뗌, 2초 후 해제)
[12:14]이동 X/Y/Z (자이로)부호 있는 8bit. I2C 센서 연결 시 센서값으로 대체됨
[15:17]기울기 X/Y/Z (가속도)−90~90도로 변환 (Y축은 부호 반전)
[18]근접(Proximity)부호 없는 값
[19]확장포트 AD값부호 없는 8bit. I2C 센서 연결 시 센서값으로 대체됨
[0] 으로 큐브를 구분합니다. 큐브를 여러 대 연결하면 집계기는 20바이트 블록을 큐브 수만큼 이어붙인 한 프레임(20 × N 바이트)으로 올려 보냅니다. 각 블록의 첫 바이트 하위 니블이 그 블록의 주인입니다 — 큐브번호 = Data[i×20] & 0x0F. 블록 순서는 보장되지 않으므로 순서가 아니라 이 값으로 찾아야 합니다. 같은 값이 [3] 에도 실리지만, 84·85 양쪽에서 안전한 쪽은 [0] 입니다.
// 3대 연결 시 60바이트 프레임 파싱
for (let i = 0; i < frame.length / 20; i++) {
  const b = frame.subarray(i * 20, i * 20 + 20);
  const cube = b[0] & 0x0F;        // 0 = 집계기(Star)
  if (cube >= 8) continue;         // 미배정/끊김 블록은 건너뛴다
  sensors[cube] = parse(b);
}
가상색인덱스와 같은 값이라 [0] 은 곧 「멀티롤 · 집계」 절의 가상색(0=RED … 7=ORANGERED)을 가리킵니다. 아직 집계기에 배정되지 않았거나 단독 연결 상태면 0x0E(WHITE·미배정), 끊기면 0x0F(BLACK)가 들어가 [0]0xAE·0xAF 로 보일 수 있습니다. 펌웨어도 이 경우를 유효 큐브에서 제외합니다.
85 신규 필드[1]배터리 · [2]펌웨어 빌드 · [5]구분자 0x77 이 새로 실립니다. 세 자리 모두 84에서 앱이 쓰지 않던 값(가상큐브ID 일부 · 그룹ID · TaskId)이라 기존 파싱을 깨지 않습니다. 큐브 번호 자리인 [3]도 84와 동일하게 유지됩니다. 이 값들은 큐브이노·마이크로비트가 연결되지 않은 경우에만 채워지며, 연결된 경우 84와 완전히 동일하게 동작합니다. [5]==0x77 인지로 85 이상 여부를 판별할 수 있습니다.

I2C 센서별 바이트 매핑 (Method 1)

확장포트에 I2C 장치가 인식되면 [10]·[12]~[14]·[19] 가 해당 센서 값으로 대체됩니다. 장치는 부팅 시 자동 검출되며, 앱에서 0xA2 Task 0000·[9]=0x80 으로 현재 인식된 주소를 조회할 수 있습니다.

센서I2C 주소[10][12][13][14][19]갱신
초음파 거리0x15거리 2~255 cm매 주기
온도 (MCP9805)0x18소수부 (÷4)정수부 ℃매 주기
컬러 (VEML6040)0x10RGB컬러키 0~7매 주기
온습도 DHT200x38습도 %온도 ℃매 주기
CO₂ SCD400x62온도습도CO₂ LSBCO₂ MSB5초
미세먼지 SPS300x69PM1.0PM2.5PM4.0PM101초
미세먼지 PM20080x28PM1.0PM2.5PM101초
인터랙션 모듈 TCA95340x20버튼 1=눌림, 0=뗌매 주기
AI 카메라 (허스키렌즈·오즈아이)0x32X 상위X 하위Y 상위Y 하위학습 ID매 주기
(I2C 장치 없음)AIN 하위니블자이로 X/Y/Z확장포트 AIN 8bit매 주기
PM 값은 µg/m³ 를 0~255 로 포화 변환합니다(255 초과는 255로 고정).
SCD40·SPS30·PM2008은 워밍업·측정 미완료 시 직전값을 유지합니다(0으로 떨어지지 않음).
TCA9534는 하드웨어가 active-low(눌림=0)이지만 펌웨어가 반전해 1=눌림으로 올려 보냅니다.
AI 카메라는 블록(물체) 인식 시 블록 중심 좌표와 ID를, 화살표(라인) 인식 시 목표점 좌표와 ID를 실어 보냅니다. 인식된 대상이 없으면 값이 갱신되지 않습니다.
Method 0(기본, 28바이트)는 자이로·가속도·근접·확장을 각 16bit 빅엔디언으로 제공합니다. IMU 해상도: 가속도 16384 LSB/g(±2g), 자이로 32.8 LSB/(deg/s)(±1000deg/s). 온도 = (hi<<8|lo)×(1/512)+23 ℃.

SET GetPeripherals 0xBA — 11바이트

버튼/컬러LED/근접/IMU/배터리를 개별 조회. [9]=InActionId, [10]=Property.

[9] InActionId대상[10] Property 예
C3컬러LED2=인덱스
B0버튼0=상태
B1근접0=Mavg, 4=전압
B3IMU2=Acc+Gyro, 4/5=온도
B4배터리1=전압+퍼센트
A1IMU 과열 경보

배터리 조회 예(스크래치 checkBattery): FF FF FF [cube] 00 00 BA 00 0B B4 01. 응답 [6]=BA, [9]=B4, [13]=배터리 %.

아두이노 · 큐브이노 · 라인봇

이 탭은 BLE 계층(호스트 → G큐브)에서 아두이노/큐브이노 기능을 제어하는 OpCode(0xA3·0xA2·0xA4)를 설명합니다. G큐브에 직접 꽂힌 아두이노 보드(큐브이노)가 시리얼(UART)로 G큐브와 주고받는 하위 프로토콜은 별도의 「큐브이노(시리얼)」 탭을 참조하세요.

SET Cubino/Arduino/Microbit 0xA3

확장 보드(큐브이노 등) 제어. Task ID로 기능 구분.

Task ID기능값 [10],[11]
00EA아날로그 읽기 시작샘플링주기, analogFlag
00EB디지털 포트dPortH(D8~D13), dPortL(D4~D7)
00EC수중 온도센서(DS18B20) 시작dummy
00ED미세먼지(GP2Y1010) 시작dummy
00EESPS30 시작dummy
00EFSCD40(CO2) 시작dummy
00E0PM2008 시작dummy
00E3인터랙션 모듈 RGB LED[10]=rgb 비트마스크

응답: Mandatory 10바이트 + A5~A0 아날로그 6바이트(8bit/ch) = 16바이트.

SET 인터랙션 모듈 RGB LED 0xA3 · Task 00E3 — 11바이트

TCA9534 확장보드(I2C 0x20)의 RGB LED를 켭니다. 모듈이 인식된 상태에서만 동작합니다.

FF FF FF[0:2]
CUBE[3] 큐브
00 E3[4:5] Task
A3[6]
00 0B[7:8]=11
00[9] 미사용
RGB[10] 색
[10] 값bit0 Rbit1 Gbit2 B
0x00···소등
0x01ON··빨강
0x02·ON·초록
0x03ONON·노랑
0x04··ON파랑
0x05ON·ON자홍
0x06·ONON청록
0x07ONONON백색
// 예시) 0번 큐브의 인터랙션 모듈을 파랑(bit2)으로
FF FF FF 00 00 E3 A3 00 0B 00 04
LED는 on/off 3비트라 표현 가능한 색이 8가지뿐입니다. 밝기·중간색은 하드웨어상 불가하며, 중간색이 필요하면 큐브 본체 컬러 LED(0xCE)를 쓰세요. 버튼 입력은 센서 스트림(0xB8) [19] 로 올라옵니다.

SET AI 카메라 제어 모드 0xAD · Task 00BC

허스키렌즈·오즈아이(I2C 0x32)가 연결된 큐브를 AI 카메라 제어 모드로 전환합니다. 부팅 시 라인(화살표)이 학습되어 있으면 자동으로 이 모드에 진입하며, 같은 그룹번호의 G큐브 2대만 연결됩니다. 0xBA GetPeripherals 응답의 종류 코드가 0xBC 로 회신되면 AI 카메라 모드입니다.

SET ArduinoI2C 0xA2

I2C 장치 질의/제어. [9]=I2C PortStatus(80=연결주소 질의, 8D=DIO 설정). LED매트릭스(주소 0x70)는 「LED·디스플레이」 탭 참조.

Task ID기능
0000연결 주소 질의
00D1~00D3SetDIN / SetDOUT / SetDIO
00D4리모컨 실드
00D5초음파 거리

연결 주소 질의는 FF FF FF [cube] 00 00 A2 00 0B 80 로 보내며, 응답 [9] 에 현재 인식된 I2C 주소가 실립니다(미인식 시 0).

응답 [9]장치비고
0x00없음서보·확장포트 AIN·리모컨이 대신 활성화됨
0x07큐브이노 (Cubeino)UART 보드
0x10컬러 센서 VEML6040
0x15초음파 거리
0x18온도 MCP9805
0x20인터랙션 모듈 TCA9534버튼 + RGB LED. 확장보드
0x28미세먼지 PM2008
0x32AI 카메라 (허스키렌즈·오즈아이)
0x38온습도 DHT20
0x62CO₂ SCD40Task 00EF 로 시작
0x69미세먼지 SPS30Task 00EE 로 시작
0x70도트매트릭스 HT16K33「LED·디스플레이」 탭 참조
I2C 장치는 부팅 시 1회만 자동 검출됩니다. 동작 중에 새로 꽂은 장치는 인식되지 않으므로 큐브를 재부팅해야 합니다. 다만 도트매트릭스는 FW 1.0.4.85부터 인식된 뒤 뺐다 다시 꽂으면 0.5초 내에 자동 복구됩니다.

SET Control LineBot 0xA4 — 11바이트

라인봇(트레이서보드) 전용. Task ID로 동작, [9][10]에 파라미터.

라인봇은 라인봇 링크를 조립하고 G큐브와 라인보드를 연결한 뒤, G큐브를 통해 제어 모드(Task 00A6, PortStatus[9]=1)로 설정하면 이후 아래 명령들로 라인봇을 직접 제어할 수 있습니다.
Task ID동작파라미터
00A6자동/제어 모드[9]=1(HIGH)/0(LOW)
00A7속도 제어[9]=G0속도, [10]=G1속도
00B7이동[9]=속도(−100~100), [10]=cm
00C7회전[9]=속도, [10]=각도
00D7색 만날 때까지 라인추종[9]=색키
00D8격자 통과까지 라인추종[9]=격자수
00E7라인 감지까지 회전[9]=0(좌)/1(우)/2(U턴)
00F7그리퍼[9]=1(열기)/0(닫기)

스크래치 orderLineBot(taskID, p9, p10): FF FF FF 00 00 [taskID] A4 00 0B [p9] [p10]

큐브이노 (Cubeino) — 시리얼 프로토콜

큐브이노는 G큐브의 확장포트에 꽂히는 아두이노 호환 보드입니다. 여기서의 통신은 BLE가 아니라 G큐브 MCU와의 시리얼(UART) 연결이며, 모든 명령이 10바이트 고정 패킷으로 오갑니다.

두 프로토콜 계층 관계

계층구간전송패킷
BLE 프로토콜호스트(PC/앱) ↔ G큐브(Star)BLE NUS가변, 최대 20B, FF FF ...
큐브이노 시리얼아두이노 보드 ↔ 꽂힌 G큐브UART Serial고정 10바이트

즉, 큐브이노는 자신이 꽂힌 G큐브를 시리얼로 직접 제어하고, 그 G큐브가 다시 BLE 멀티롤로 다른 큐브들과 연동됩니다. 큐브이노 보드가 센서 허브(0x71)나 라인보드(0x72)로도 동작할 수 있습니다.

패킷 형식 (10바이트 고정)

CMD[0] 명령
IV[1] 검증
P2[2]
P3[3]
P4[4]
P5[5]
P6[6]
P7[7]
P8[8]
P9[9]
[0] Command명령 코드(아래 표)
[1] IV (검증바이트)get_iv(cmd) = 상·하위 니블 스왑. (cmd>>4) | ((cmd&0x0F)<<4)
[2:9] Parameters명령별 인자(미사용 시 0)
// 검증바이트 계산 (Cubeino.cpp)
byte get_iv(byte a){ return (a/16) + (a & 0x0F)*16; }   // 니블 스왑
// 예: 0x30 → 0x03,  0xA0 → 0x0A,  0x51 → 0x15

// 전송: send_gcube()는 10바이트를 쓰고 50ms 지연
//       direct_send_gcube()는 10바이트를 쓰고 1ms 지연(라인보드 등 저지연)
여러 큐브 대상 명령은 명령 코드에 큐브 인덱스를 더해 구분합니다(예: 자이로 0xA0 + 큐브번호, 매트릭스 이미지 0x51 + 인덱스). IV 바이트도 더해진 최종 코드로 계산합니다.

연결·핸드셰이크

명령코드동작
GET_BOARD_ID0x10G큐브가 보드 종류를 물음 → 보드가 자기 ID로 응답
GET_CUBE_NUMBER0x21Star에 연결된 큐브 개수 요청
SET_ALL_CUBES_CONNECTED0x2A모든 큐브 연결 완료 통지(→ G큐브가 센서 수집 시작)
CONTROL_COMMAND0x1F리부트 등 제어(켜져 있던 큐브 초기화)

보드 ID: Cubeino 0x70, Sensor Hub 0x71, LineBoard 0x72, 점자보드(마이크로비트) 0x73 85+.

G큐브는 0x10 응답으로 받은 보드 ID에 따라 동작 모드를 정합니다. 0x72·0x73 은 TracingMode(라인 주행)로 진입하고, 0x70·0x71 은 센서 허브 모드로 동작합니다.

G큐브 센서 읽기 (아두이노 → G큐브)

기능코드비고
기울기/자이로0xA0 + 큐브#3바이트 응답(부호 8bit ×3)
근접센서0xAA파라미터에 큐브번호
버튼 상태0xAB파라미터에 큐브번호
확장포트 2바이트 아날로그0xB0 + 큐브#16bit 값([1]×16 + [2])
확장 컬러센서 색키0xE0 + 큐브#color key
확장 컬러센서 RGB0xE8 + 큐브#color value
Echo(연결확인)0xFF단순 에코

확장 센서 종류 코드 (s_name)

0xC1소리 0xC2볼륨 0xC3자석 0xC4
초음파 SEN-0307(520cm) 0xC5온도 0xC6컬러키 0xC7컬러값 0xC8
초음파 US-016(100cm) 0xC9아날로그 0xF7초음파는 값×범위/4095로 cm 환산

G큐브 구동 제어 (아두이노 → G큐브)

기능코드파라미터
단일 모터 속도0x30큐브번호, 속도(−100~100)
다중 모터 속도0x31 + (N−2)s7…s0 (역순)
단일 모터 각도0x38큐브번호, 각도(×100/18, 2바이트)
다중 모터 각도0x39 + (N−2)s3…s0 각 2바이트
단일 서보 각도0x40큐브번호, 각도(0번은 보드 자체 서보)
다중 서보 각도0x41 + (N−2)s7…s0
매트릭스 이미지(8B)0x51 + 인덱스8바이트 비트맵
매트릭스 깜빡임0x5A인덱스, blinkrate
매트릭스 픽셀0x60인덱스, x, y, on/off
매트릭스 문자열0x61 + 인덱스8바이트

모터 속도는 char(−100~100). 다중 명령의 큐브 수 N은 Star 포함 총 개수, 코드에 N−2를 더합니다.

큐브이노 자체 입출력 / 센서 허브

기능코드설명
아날로그 보고0x11보드의 A0~A3를 G큐브로 보고(A3·A2 8bit, A1·A0 10bit)
디지털 포트 설정0x1AD4~D7(vl b4~b7), D8~D13(vh b2~b7)
큐브-아두이노 아날로그 값0xD0 + 큐브#포트 지정, 16bit 응답
큐브-아두이노 아날로그 시작0xD8보고 시작
큐브-아두이노 디지털 제어0xD9port_hi, port_lo

라인봇(LineBoard) 전용 · 로봇 모델

기능코드
센서 데이터 / 속도 / 이동 / 회전0x12 / 0x13 / 0x14 / 0x15
색까지 이동 / 라인까지 회전 / 그리퍼 / 격자 이동0x16 / 0x17 / 0x18 / 0x19
멜로디 / 넘어짐 / 리부트0x1B / 0x1C / 0x1E

핑퐁로봇 모델(기어 유형)

DrawingBot 0(기어 없음), AutoCar 1(기어), AntBot·BattleBot 2(기어). move_pingpong_robot·rotate_pingpong_robot가 모델별 보정계수로 다중 모터 각도를 계산합니다. 펜/레버/그리퍼 업다운은 0번 큐브 각도(±90/±180)로 구현됩니다.

Cubeino 라이브러리 주요 API

모든 함수는 마지막 인자로 G큐브와 연결된 시리얼 포트 Stream &cubeSerial을 받습니다. 아래 표의 시그니처에서는 지면상 이 인자를 생략했습니다. 공통 규약: cube_number는 연결 순번(0부터), GCUBE_TOTAL은 Star를 포함한 전체 큐브 수, 속도는 -100~100입니다.

연결 · 초기화

함수설명
wait_for_first_cube_connected()첫 G큐브의 켜짐(보드ID 요청 0x10)을 대기하고 큐브이노 보드ID 0x70으로 응답합니다. 이미 켜져 있던 큐브는 리부트시켜 초기 상태로 만듭니다.
wait_for_hub_cube_connected()동일하게 대기하되 센서 허브 보드ID 0x71로 응답합니다.
wait_for_lineboard_cube_connected(s1, s2, mode)두 시리얼 포트에 연결된 라인보드용 큐브를 대기하고 라인보드 보드ID 0x72로 응답합니다. mode는 연결을 기다릴 큐브 수.
wait_for_all_cube_connected(GCUBE_TOTAL)Star에 연결된 큐브 수(0x21)를 주기적으로 물어 GCUBE_TOTAL개가 될 때까지 대기한 뒤, 전체 연결 완료(0x2A)를 통지합니다 → G큐브가 센서 수집을 시작.
check_main_cube_reconnected(GCUBE_TOTAL) → bool메인 큐브의 하드웨어 연결을 확인합니다. 미연결이면 false(시리얼 전송 중단 안내용), 연결·재부팅 감지 시 보드ID 재전송 후 전체 연결을 다시 대기합니다.

센서 읽기 (G큐브 → 아두이노)

함수설명
get_cube_acc_value(int8_t mdata[], cube_number)큐브의 기울기/자이로 3축을 읽어 mdata[0..2]부호 있는 8bit로 저장합니다.
get_cube_proxi_value(byte mdata[], cube_number)근접센서 값을 3바이트 응답으로 읽습니다.
get_cube_button_value(byte mdata[], cube_number)버튼 상태를 읽습니다(mdata[2]가 눌림 여부).
get_cube_ext_sensor_value(byte mdata[], cube_number, s_name) → uint16_t확장포트 센서를 읽고 종류(s_name)에 맞게 환산한 값을 반환합니다. 예: 초음파는 cm(SEN-0307 0~520, US-016 0~100), 온도·빛·소리·볼륨·자석·아날로그는 정수. s_name: 빛 0xC1·소리 0xC2·볼륨 0xC3·자석 0xC4·초음파(흑) 0xC5·온도 0xC6·컬러키 0xC7·컬러값 0xC8·초음파(녹) 0xC9·아날로그 0xF7.
get_cube_echo_value(byte mdata[])연결 확인용 에코(0xFF)를 보내고 3바이트 응답을 받습니다.
get_cube_command(byte mdata[])큐브가 보낸 3바이트 명령을 그대로 읽습니다(저수준).

모터 · 서보 제어 (아두이노 → G큐브)

함수설명
send_a_cube_speed(cube_number, speed)지정 큐브 모터를 속도 -100~100으로 연속 회전(부호=방향).
send_multi_cube_speed(GCUBE_TOTAL, s0…s7)여러 큐브 모터 속도를 한 번에 지정.
stop_all_cube_motor(GCUBE_TOTAL)모든 큐브 모터를 정지(속도 0 일괄 전송).
send_a_cube_angle(cube_number, angle)지정 큐브를 상대 각도만큼 회전(내부적으로 100×angle/18 스텝 환산).
send_multi_cube_angle(cube_range, s0…s3)여러 큐브 각도를 한 번에 지정.
send_a_servo_angle(cube_number, angle, svo)서보 각도 설정. cube_number==0이면 보드에 직접 연결된 서보(svo)를, 그 외에는 해당 큐브의 서보를 제어.
send_multi_servo_angle(GCUBE_TOTAL, s0…s7, svo)여러 서보 각도를 한 번에 지정(0번은 보드 서보).

도트매트릭스

함수설명
send_matrix_still_image(cube_index, m[8])8바이트 비트맵 정지 이미지를 표시.
start_matrix_roll_image(GCUBE_TOTAL, m[], duration)여러 매트릭스 큐브에 걸쳐 이미지를 흐르게 표시(duration초).
start_matrix_shift_image(cube_index, GCUBE_TOTAL, img[], xp, yp)기울기 값(xp, yp: −90~90)에 따라 이미지를 상하좌우로 이동시켜 표시.
set_matrix_pixel_data(cube_index, x, y, onoff)특정 픽셀(x, y) 하나를 켜고/끔.
set_matrix_string_data(cube_index, s0…s7)문자열/패턴 데이터를 매트릭스에 표시.
set_matrix_blink_rate(cube_index, blinkrate)깜빡임 속도 설정.

핑퐁로봇 모델 · 액추에이터

함수설명
move_pingpong_robot(gear_flag, distance)로봇을 distance(cm)만큼 직진/후진. gear_flag(모델)에 따라 보정계수가 달라집니다: DrawingBot 0(기어 없음), AutoCar 1, AntBot·BattleBot 2.
rotate_pingpong_robot(gear_flag, angle)로봇을 angle(도)만큼 제자리 회전(모델별 보정).
pen_up() / pen_down()드로잉봇 펜을 올리고/내림(0번 큐브 ±90°).
lever_up() / lever_down()레버를 올리고/내림(0번 큐브 ±90°).
gripper_open() / gripper_close()그리퍼를 열고/닫음(0번 큐브 ±180°).

아두이노 자체 입출력 · 저수준

함수설명
report_analog_data()보드의 A0~A3 아날로그 값을 읽어 G큐브로 보고(0x11). A3·A2는 8bit, A1·A0는 10bit.
set_digital_port(vh, vl)보드의 디지털 출력 설정: D4~D7은 vl의 b4~b7, D8~D13은 vh의 b2~b7.
set_cube_arduino_analog_start(cube_number)지정 큐브에 꽂힌 아두이노의 아날로그 보고를 시작.
get_cube_arduino_analog_value(cube_number, a_port) → uint16_t지정 큐브-아두이노의 특정 포트 아날로그 값(16bit)을 읽음.
set_cube_arduino_digital_value(cube_number, port_hi, port_lo)지정 큐브-아두이노의 디지털 포트를 제어.
send_gcube(x0…x9) / direct_send_gcube(x0…x9)10바이트 패킷을 직접 전송하는 저수준 함수. 앞은 50ms, 뒤는 1ms 지연(라인보드 등 저지연용).
get_iv(cmd) → byte명령의 검증바이트(상·하위 니블 스왑)를 계산.

라인보드(라인봇) 전용

함수설명
direct_cube_speed_control(speed, colorkey, distance)라인 추종 속도/색/거리 제어(저지연 전송).
direct_cube_move_control(speed, distance)지정 거리(최대 200cm) 이동. 거리 부호로 방향 결정.
direct_cube_rotate_control(speed, angle)지정 각도(최대 180°) 회전.
direct_cube_melody_control(melody_number, m1, m2, m3)라인봇 멜로디 재생.
report_sensor_data(colorkey, distance)라인보드 센서 데이터를 보고.