Protocol

BLE 연결

S큐브는 Nordic UART Service(NUS)를 광고합니다. 각 큐브가 호스트에 직접 연결되며(Peripheral), 릴레이·멀티롤은 없습니다. 대상 펌웨어 v0.0.45.0.

GATT UUID

구분UUID속성방향
Service (NUS)6e400001-b5a3-f393-e0a9-e50e24dcca9e——
명령 특성6e400002-b5a3-f393-e0a9-e50e24dcca9eWrite / WriteWithoutResponse호스트→큐브
데이터 특성6e400003-b5a3-f393-e0a9-e50e24dcca9eNotify큐브→호스트

G큐브 문서와 같은 기준입니다: ...0002에 명령을 쓰고, ...0003의 Notify로 데이터를 받습니다.

기기 탐색(스캔)

광고 이름은 SCUBE.XX.AIR입니다. XX는 그룹번호(16진 2자리, 자리마다 0~9, 출고 기본 00)입니다.

// Web Bluetooth 스캔 필터 예
filters: [{ namePrefix: "SCUBE." }]          // 전체
filters: [{ name: "SCUBE.23.AIR" }]          // 그룹 23만
optionalServices: ["6e400001-b5a3-f393-e0a9-e50e24dcca9e"]

연결 후 표준 시퀀스

  1. NUS 연결 → ...0003 Notify 구독 — 구독 즉시 20바이트 센서 레코드가 200Hz로 흐릅니다.
  2. 구독 직후 큐브가 통보 2개를 보냅니다: 현재 주기 0xF7, 확장 센서 종류 0xFF.
  3. 여러 큐브를 쓰면 연결 순서대로 [0x10][n](n=1~3)으로 순번을 지정합니다.
  4. 필요 시 펌웨어 정보 [0x2D], 그룹번호 조회 [0x23].
명령은 G큐브와 달리 공통 헤더가 없습니다. 형식은 [명령ID 1B][인자…]이며, 알 수 없는 명령은 무시됩니다.

수신 프레임 구분 — 길이로 판별

Notify 길이종류처리
20의 배수센서 레코드20바이트 단위로 잘라 해석 (현재는 알림 1회 = 레코드 1개)
그 외통보·응답 프레임첫 바이트가 프레임 ID (0xEC~0xFF)
레코드 [6]은 연결 순번에 따라 값이 바뀌므로 프레임 식별에 쓰지 마세요. 항상 길이로 구분합니다.

센서 레코드 (20바이트)

구독 중 큐브가 연속으로 보내는 기본 데이터입니다. 모든 다바이트 값은 Little Endian입니다. 20바이트는 BLE 무선 슬롯 하나에 정확히 맞는 크기입니다.

바이트 맵

GX[0:1] gyro_x
GY[2:3] gyro_y
GZ[4:5] gyro_z
OP[6] 순번
EX[7:8] ext
E1[9] ext1
E2[10] ext2
AX[11:12] accel_x
AY[13:14] accel_y
AZ[15:16] accel_z
PX[17] prox
BB[18] 버튼+배터리
CK[19] checksum
바이트필드타입설명
[0:5]gyro x·y·zint16 ×3자이로 원시값
[6]OP + 순번uint80x77=미지정 · 0x78=1번 · 0x79=2번 · 0x7A=3번
[7:8]ext 대표값uint16확장 포트 값. 아날로그면 12bit 원시값(0~4095), I2C 센서면 그 센서의 대표값 (「확장 센서」 탭)
[9:10]ext 보조값uint8 ×2다중값 센서의 추가 값. 그 외에는 0
[11:16]accel x·y·zint16 ×3가속도 원시값
[17]proxuint8근접센서 반사 세기 0~255 (평상시 ≈0, 가리면 커짐)
[18]button + batterybit7:6 / bit5:0버튼 = r[18] >> 6 (0=없음 1=눌림 2=뗌) · 배터리 % = round((r[18] & 0x3F) × 100 ÷ 63)
[19]checksumuint8CRC16-CCITT(rec[0:18])의 하위 1바이트 (poly 0x1021, init 0xFFFF)

눈금 변환

센서범위변환
자이로±2000 dpsdps = raw ÷ 16.384
가속도 (기본)±4 gg = raw ÷ 8192
가속도±16 gg = raw ÷ 2048

가속도 범위는 [0x1B]로 바꾸며, 연결이 끊기면 ±4 g로 돌아갑니다.

갱신 주기

필드측정 주기
gyro · accel레코드마다 (기본 5ms = 200Hz, 동시 측정)
prox약 10Hz (최신값 유지)
battery6초마다 + 평활
ext센서마다 다름 (최신값 유지)

버튼 이벤트

  • 눌림(1)·뗌(2)이 각각 정확히 한 번 실립니다. 눌린 동안 반복되지 않습니다.
  • 버튼 이벤트 시 레코드가 추가로 즉시 나갈 수 있어, 도착 간격이 항상 일정하지는 않습니다.
  • 2초 길게 누르면 전원이 꺼지고 연결이 끊깁니다(별도 이벤트 없음).

파싱 예제 (Python)

import struct

def parse_record(r: bytes):
    gx, gy, gz = struct.unpack_from('<hhh', r, 0)
    cube_no    = r[6] - 0x77                 # 0=미지정, 1~3
    ext,       = struct.unpack_from('<H', r, 7)
    ax, ay, az = struct.unpack_from('<hhh', r, 11)
    return {
        'gyro_dps': (gx/16.384, gy/16.384, gz/16.384),
        'accel_g':  (ax/8192, ay/8192, az/8192),   # ±4g 기준
        'cube': cube_no, 'ext': ext, 'ext1': r[9], 'ext2': r[10],
        'prox': r[17],
        'button': r[18] >> 6,
        'battery': round((r[18] & 0x3F) * 100 / 63),
    }

def on_notify(data: bytes):
    if len(data) % 20 == 0:                      # 센서 레코드
        for i in range(0, len(data), 20):
            print(parse_record(data[i:i+20]))
    else:                                        # 통보 프레임
        print('frame', hex(data[0]), data.hex(' '))

명령·프레임 목록

호스트→큐브 명령(CMD)과 큐브→호스트 통보 프레임(FRAME) 전체입니다. 진단용 명령은 「진단·성능」 탭에 있습니다.

명령 (호스트 → 큐브)

ID이름인자응답분류
0x10연결 순번 지정[n] 1~3—연결
0x11샘플 주기[ms] 5~1000xF7스트리밍
0x12연결 간격 요청[1.25ms 단위] 6~12—스트리밍
0x1B가속도 범위[4|16]0xF6스트리밍
0x1C자동 감속 끔/켬[0|1]0xF7스트리밍
0x24I2C 센서 조회/재검사없음 / [1]0xFF확장 센서
0x25확장 포트 값 선택[n] 0~8 / 없음=조회0xFF확장 센서
0xEE확장 센서 상세값없음0xEE확장 센서
0x1A효과음 재생[id]([vol])—출력
0x2B임의 주파수 톤[Hz u16][ms u16]([vol])—출력
0x26LED 매트릭스 그리기[행0..행7]([밝기])—출력
0x2C인터랙션 모듈 LED[비트마스크]레코드 [8]출력
0x27스텝모터 각도 이동[각도 i16][속도 1~10]0xEF출력
0x28스텝모터 연속 회전/정지[속도 i8] / 없음=정지0xEF출력
0x23그룹번호 지정/조회[BCD] / 없음=조회0xFE시스템
0x29원격 전원 OFF[0x5A]연결 끊김시스템
0x2D펌웨어 정보없음0xEC시스템
0x20소리 녹음 (1.28초)[AIN 0~3]([flags])0xFA + 0xF9소리 입력
0x21음성 모드 진입/해제[0|1]0xFC소리 입력
0x22연속 소리 스트리밍[ch]([형식]) / 없음=정지0xFD + 0xF9소리 입력

통보 프레임 (큐브 → 호스트)

ID이름길이구조
0xEC펌웨어 정보18B[EC][형식][FW a.b.c.d][YY MM DD][SD FWID u16][IMU칩][BLE 주소 6B]
0xEE확장 센서 상세값2~10B[EE][종류][값1 u16][값2 u16]…
0xEF스텝모터 상태4B[EF][상태][남은 각도 i16]
0xF6가속도 범위2B[F6][범위 g]
0xF7주기 통보3B[F7][주기 ms][사유]
0xF9소리 데이터19B[F9][순번 u16][데이터 16B]
0xFA녹음 요약17B상태·샘플수·최소/최대·DC·RMS·표본율·형식
0xFC음성 모드 통보3B[FC][모드][사유]
0xFD스트리밍 시작/종료5B / 7B시작 [FD][1][ch][표본율 u16] · 종료 [FD][0][사유][프레임수 u32]
0xFE그룹번호4B[FE][저장값][광고중 값][상태]
0xFF확장 센서 종류5B[FF][종류][주소][대표값 u16]

진단 응답 0xED · 0xF1~0xF5 · 0xF8 · 0xFB는 「진단·성능」 탭을 참조하세요.

확장 센서

I2C 센서는 부팅 시 자동 감지하고, 아날로그·1-Wire 센서는 호스트가 [0x25]로 선택합니다. 어떤 센서인지는 0xFF 프레임이 알려주며, 값은 레코드 [7:10]에 실립니다.

CMDI2C 센서 조회 0x24

보내기동작
24현재 감지 상태 조회
24 01다시 찾기 (센서를 바꿔 꽂았을 때)

재검사 동안(수십 ms) 레코드가 잠시 멈춥니다. 센서를 빼면 자동으로 감지 해제됩니다.

CMD확장 포트 값 선택 0x25

n센서종류 코드
0선택 안 함 (I2C 자동 감지)—
1방수온도 (DS18B20, 1-Wire)14
2아날로그 원시값15
3~8빛 · 소리 · 볼륨 · 자석 · 초음파 · 인체감지16~21

선택은 저장되어 재부팅 후에도 유지되고 즉시 적용됩니다. 인자 없이 보내면 조회.

FRAME확장 센서 종류 0xFF — 5바이트

FF[0] ID
KD[1] 종류
AD[2] I2C 주소
VL[3] 대표값 Lo
VH[4] 대표값 Hi

연결 직후, 감지 상태가 바뀔 때, [0x24]·[0x25] 응답으로 옵니다. 이 프레임을 받기 전의 ext 값은 해석하지 마세요. 방수온도(14)는 주소 칸이 연결 여부(1=연결, 0=미연결)입니다.

종류 코드와 레코드 적재 규칙

종류센서 (주소)[7:8] 대표값[9][10]
0없음아날로그 원시 0~409500
2온도 (0x18)13bit 원시, ÷16 = ℃00
3컬러 VEML6040 (0x10)R 상위8bit / GBC
5SCD40 (0x62)CO₂ ppm온도 ℃+45습도 %
7인터랙션 모듈 TCA9534 (0x20)[7] 버튼 0/1 · [8] 켜진 LED00
8SPS30 미세먼지 (0x69)PM2.5 ㎍/㎥PM10 u16
9LED 매트릭스 HT16K33 (0x70)출력 장치 — [0x26]로 그리기
10큐브이노 (0x07)보드 ID00
11HuskyLens (0x32)인식된 대상 수00
12BMP280 기압 (0x76/0x77)기압 0.1 hPa온도 i16 (0.01℃)
13INA219 전류 (0x40)전류 mA (i16)전압 0.1V전력 0.1W
14방수온도 DS18B20 (1-Wire)온도 0.1℃ (i16)00
15아날로그 원시 (선택됨)0~409500
16~21빛·소리·볼륨·자석·초음파·인체감지0~409500
22BH1750 조도 (0x23/0x5C)조도 lx00
23TSL2561 조도 (0x39/0x29/0x49)조도 lx적외(CH1) u16
24MAX30102 심박 (0x57)심박수 BPM (0=측정 전)bit7 손가락 · bit0~6 SpO₂ %맥파 i8

1·4·6은 예약 번호입니다. 1바이트 칸은 정밀도를 줄인 값이므로 정밀값은 0xEE를 사용하세요. 심박·산소포화도는 교육용 추정값이며 의료용이 아닙니다.

CMD상세값 요청 0xEE → FRAME0xEE

요청할 때만 응답합니다. 응답은 [EE][종류][값1 u16 LE][값2 u16 LE]…이며, 값 개수는 종류가 정합니다. 상세값이 없는 센서는 [EE][종류] 2바이트만 옵니다.

센서값 (순서대로)
컬러R, G, B, C
SCD40CO₂ ppm, 온도 0.1℃, 습도 0.1%
SPS30PM1.0, PM2.5, PM4.0, PM10
BMP280기압 0.1 hPa, 온도 0.01℃
INA219전류 mA, 전압 mV, 전력 mW
BH1750조도 lx
TSL2561조도 lx, CH0(가시+적외), CH1(적외)
MAX30102심박 BPM, SpO₂ %, IR 직류(÷16), 맥파 진폭
아날로그 출력 센서(소리·빛·볼륨 등)가 꽂혀 있으면 SDA 선이 아날로그 전압에 묶여 I2C가 동작할 수 없습니다. 이때 0xFF 종류가 0(없음)으로 오면 [0x25]로 아날로그 센서를 선택하세요.

출력 장치

소리·LED 출력 명령은 응답이 없습니다. 스텝모터만 상태 통보 0xEF를 보냅니다.

CMD효과음 0x1A — 2~3바이트

[1A][id]([vol]) · vol = 1~50 (생략 시 8)

id소리
0시작 (1318Hz)
1저장 (659Hz)
2성공 (659→988Hz)
3거절 (220Hz)

CMD임의 주파수 톤 0x2B — 5~6바이트

[2B][주파수 u16][길이 ms u16]([vol])

  • 범위 50~4000Hz · 10~5000ms · vol 1~50
  • 주파수 0 = 즉시 정지
2B B8 01 F4 01      // 440Hz, 500ms

CMDLED 매트릭스 그리기 0x26 — 9~10바이트

[26][행0]…[행7]([밝기 0~15]) · 행0 = 맨 위, 각 행의 bit0 = 왼쪽 칸. 매트릭스(종류 9)가 감지된 경우에만 동작합니다.

26 00 66 FF FF 7E 3C 18 00 08     // 하트, 밝기 8

한 번 그리는 데 약 2ms가 걸리므로 연속 갱신은 100ms 정도 간격을 권장합니다.

CMD인터랙션 모듈 LED 0x2C — 2바이트

[2C][마스크] · bit0 = 빨강, bit1 = 초록, bit2 = 파랑 (1 = 켜짐, 8가지 색)

2C 00   // 끄기      2C 01   // 빨강
2C 06   // 청록      2C 07   // 흰색

응답 대신 현재 켜진 색이 레코드 [8]에 실립니다. 보낸 값과 다르면 다시 보내세요(재시도 횟수 제한 권장).

스텝모터 (본체)

CMD각도 이동 0x27 — 4바이트

[27][각도 i16][속도 1~10] · 현재 위치 기준 상대 각도(부호 = 방향) · 0.18°/스텝 · 속도 1~10 ≈ 3~30 RPM. 이동 중 새 명령을 보내면 새 명령으로 바뀝니다.

27 5A 00 05      // +90°, 속도 5
27 A6 FF 0A      // −90°, 속도 10

CMD연속 회전 / 정지 0x28

[28][속도 i8] = 회전(부호 = 방향, 0 = 정지) · [28] = 정지. 정지하면 코일 전류가 꺼집니다(유지 토크 없음). 연결이 끊기면 자동 정지합니다.

FRAME스텝모터 상태 0xEF — 4바이트

[EF][상태][남은 각도 i16] · 상태 0 = 멈춤, 1 = 각도 이동 중, 2 = 연속 회전 중. 상태가 바뀔 때만 옵니다.

설정·시스템

CMD연결 순번 0x10 — 2바이트

[10][n] · n = 1(빨강), 2(초록), 3(파랑). 연결 LED 색과 레코드 [6](= 0x77 + n)에 반영됩니다. 연결이 끊기면 초기화됩니다.

CMD샘플 주기 0x11

[11][ms] · 5~100ms (5 = 200Hz 기본, 10 = 100Hz, 20 = 50Hz). 적용 시 0xF7 통보.

CMD연결 간격 0x12

[12][n] · n × 1.25ms (6 = 7.5ms, 12 = 15ms 기본). 호스트에 요청만 하며, 거절되면 기존 간격을 유지합니다. 연결 약 2초 뒤에 보내세요.

CMD가속도 범위 0x1B

[1B][4|16] → 0xF6 [F6][범위]. 응답을 받은 순간부터 새 눈금으로 해석하세요.

CMD자동 감속 0x1C

[1C][0|1] · 기본 켬. 전송이 밀리면 큐브가 스스로 주기를 낮추고(최저 100Hz) 0xF7로 알립니다. 200Hz가 꼭 필요하면 끄세요.

FRAME주기 통보 0xF7 — 3바이트

[F7][주기 ms][사유] · 사유 0 = 구독 시작, 1 = 명령 적용, 2 = 자동 감속, 3 = 자동 회복. 주기가 바뀌면 반드시 통보됩니다.

CMD그룹번호 0x23 → FRAME0xFE

[23][XX] = 지정 · [23] = 조회. 기기 이름 SCUBE.XX.AIR의 XX이며, 저장 후 다음 부팅부터 적용됩니다.

  • XX는 16진 2자리이며 자리마다 0~9입니다. 그룹 “23” → 23 23 전송.
  • 0x0A처럼 A~F가 들어간 값은 거부됩니다.

응답 [FE][저장값][광고 중 값][상태] · 상태 0 = 조회, 1 = 저장 완료, 2 = 같은 값, 3 = 범위 밖, 4 = 쓰기 실패. 두 값이 다르면 재부팅이 필요합니다.

→ 23 23
← FE 23 00 01      // 23 저장, 현재 00으로 광고 중 → 재부팅 후 SCUBE.23.AIR
저장 응답(상태 1)을 받은 뒤 전원을 끄세요. 그룹번호는 G큐브와 같은 위치·형식으로 저장되어, 펌웨어를 교체해도 유지됩니다.

CMD원격 전원 OFF 0x29

29 5A — 확인 바이트 0x5A가 필요합니다. 버튼 2초 누름과 같이 꺼지며 연결이 끊깁니다. 그룹번호 지정 후 바로 적용할 때 유용합니다.

CMD펌웨어 정보 0x2D → 0xEC

2D → 18바이트 응답. 1초 안에 응답이 없으면 v0.0.43.0 이하 펌웨어입니다.

FRAME펌웨어 정보 0xEC — 18바이트

바이트내용
[0]0xEC
[1]형식 (현재 1). 길이가 18 이상이면 아는 필드까지만 읽으세요.
[2:5]펌웨어 버전 4자리 (예 0.0.45.0 → 00 00 2D 00)
[6:8]빌드 날짜 YY·MM·DD (이진값)
[9:10]SoftDevice FWID (S132 v6.0.0 = 0x00A8)
[11]IMU 칩 (0 = 미상, 1 = BMI160, 2 = BMI323)
[12:17]BLE 주소 (하위 바이트 먼저)

소리 입력 (아날로그 마이크)

확장 포트의 소리센서(AIN3)로 8000Hz 소리를 받습니다. 소리 데이터는 0xF9(19바이트) 프레임으로 오며, 유실 없이 재전송됩니다.

CMD단발 녹음 0x20

[20][AIN 채널 0~3]([flags]) · 1.28초(10240샘플) 녹음 후 요약 0xFA 1개와 데이터 0xF9가 이어집니다. 소리센서는 채널 3입니다.

flags뜻
bit0요약만 전송
bit14비트 IMA ADPCM 압축 전송 (생략 시 16bit 원시)

CMD음성 모드 0x21 · 연속 스트리밍 0x22

  1. 21 01 → 음성 모드 진입 (센서 레코드 전송 정지) · 0xFC [FC][1][사유]
  2. 22 03 → 채널 3 연속 스트리밍 시작 · 0xFD 시작 통보 후 0xF9 연속
  3. 22 → 정지 (남은 데이터 모두 전송 후 0xFD 종료 통보)
  4. 21 00 → 센서 모드 복귀
형식 인자압축프레임당 표본프레임/초
생략 (기본)4비트 ADPCM32250
22비트 ADPCM64125

스트리밍 중 큐브 버튼을 짧게 누르면 정지합니다. 연결이 끊기면 센서 모드로 돌아갑니다.

FRAME소리 데이터 0xF9

[F9][순번 u16][16바이트] · 원시 = 표본 8개, 4비트 = 32개, 2비트 = 64개. ADPCM은 앞 프레임에 의존하므로 순번으로 유실을 확인하세요.

FRAME스트리밍 0xFD

시작 [FD][1][ch][표본율 u16] · 종료 [FD][0][사유][보낸 프레임 수 u32]

종료 사유 0 = 명령, 1 = 버퍼 초과, 2 = 모드 해제, 3 = 시작 거부, 4 = 하드웨어 실패, 5 = 버튼

진단·성능

진단 명령 (개발·점검용)

명령이름응답내용
0x13전송 통계0xF1 9B[F1][생성 수 u32][버린 수 u32] (구독 시점부터 누적)
0x16IMU 칩 판별0xF3 5B[F3][칩]… 1 = BMI160, 2 = BMI323
0x18링크 상태0xF4 7B[F4][LL payload][ATT_MTU u16][연결 간격 u16][PHY]
0x19배터리 원시값0xF5 6B[F5][즉시 raw u16][평활 raw u16][%] · V = raw × 7.5 ÷ 4096
0x2AIMU 영점 오프셋0xED 16B2A 읽기 · 2A 01 영점 보정 후 읽기 (큐브를 평평히 놓고)
0x14레코드 크기 (계측)—20~60B로 패딩 — 처리량 측정용
0x15 0x17근접센서 진단·조정0xF2근접센서 핀 읽기·파라미터 조정
0x1D 0x1E 0x1F하드웨어 탐지0xF8 · 0xFA · 0xFBI2C 스캔 · PDM 녹음 · 핀 부착 탐지 (보드 개발용)

진단 명령 중 일부는 실행 중 센서 레코드를 잠시 멈춥니다.

전송 성능 (Windows PC 실측)

구성권장 설정결과
큐브 1대기본 (5ms, 200Hz)약 200 레코드/초
큐브 2대연결 간격 7.5ms (12 06) + 각 100Hz (11 0A)유실 0
큐브 2대연결 간격 15ms (기본) + 각 50Hz (11 14)유실 0
  • 먼저 연결한 큐브가 대역을 적게 받는 경향이 있습니다. 동시에 여러 대를 쓰면 주기를 낮추세요.
  • 전송 큐가 차면 오래된 센서 레코드를 버립니다(최신 우선). 통보·소리 프레임은 버리지 않습니다.
  • 유실은 [0x13] 통계로 확인합니다. 레코드에는 순번 필드가 없습니다.
레코드는 20바이트를 넘지 않도록 설계되어 있습니다. 1바이트라도 넘으면 무선 슬롯을 두 개 사용해 처리량이 절반으로 떨어집니다.