คู่มือ JBand V4 SDK

Flutter | โปรโตคอล 2208A | แพ็กเก็ต 16 ไบต์

Quick Reference

BLE UUIDs

Service0000fff0-...-00805f9b34fb
Write0000fff6-...
Notify0000fff7-...

Packet Format

[CMD][Data x14][CRC] = 16 bytes
CRC = sum(bytes 0-14) & 0xFF

Key Files

ble_sdk.dartSDK methods
device_cmd.dartCommand codes
resolve_util.dartResponse parsers

Data Encoding

TimeBCD (23→0x23)
IntegersLittle-endian
Tempraw/10 = °C
Cal/Distraw/100

การใช้งาน: การเชื่อมต่ออุปกรณ์

🔗 เชื่อมต่อและเริ่มต้นอุปกรณ์
1 สแกน - กรองตามชื่อที่ขึ้นต้นด้วย "J" หรือ service UUID fff0
2 เชื่อมต่อ - เชื่อมต่อ BLE, ค้นหา services
3 เปิดการแจ้งเตือน - สมัครรับข้อมูลจาก fff7 characteristic
4 ซิงค์เวลา - 0x01 SetDeviceTime
5 อ่านข้อมูลอุปกรณ์ - แบตเตอรี่, เวอร์ชัน, MAC
// หลังจากเชื่อมต่อสำเร็จ
await sendCommand(BleSDK.SetDeviceTime(DateTime.now()));  // 0x01
await sendCommand(BleSDK.GetDeviceBatteryLevel());        // 0x13
await sendCommand(BleSDK.GetDeviceVersion());             // 0x27
await sendCommand(BleSDK.GetDeviceMacAddress());          // 0x22

// เสริม: ตั้งค่าโปรไฟล์ผู้ใช้เพื่อคำนวณแคลอรี่ให้แม่นยำ
await sendCommand(BleSDK.SetPersonalInfo(MyPersonalInfo(
  sex: 1,        // 1=ชาย, 0=หญิง
  age: 30,
  height: 175,   // ซม.
  weight: 70,    // กก.
)));

การใช้งาน: การตรวจสอบสุขภาพแบบเรียลไทม์

💓 อัตราการเต้นของหัวใจ, จำนวนก้าว, SpO2, อุณหภูมิ แบบสด

วิธีการทำงาน: เปิดการสตรีม → อุปกรณ์ส่งข้อมูลต่อเนื่อง → แยกวิเคราะห์และแสดงผล

ตัวเลือกที่ 1: การสตรีมกิจกรรมต่อเนื่อง

คำสั่งรหัสข้อมูลที่ส่งมา
RealTimeStep(true, true) 0x09 จำนวนก้าว, แคลอรี่, ระยะทาง, อัตราการเต้นของหัวใจ, อุณหภูมิ (ต่อเนื่อง)
// เริ่มการสตรีมแบบเรียลไทม์
await sendCommand(BleSDK.RealTimeStep(true, true));  // enable, tempEnable

// อุปกรณ์ส่งการตอบกลับ 0x09 ต่อเนื่อง:
// โครงสร้างการตอบกลับ (24+ ไบต์):
// [0]     = 0x09 (คำสั่ง)
// [1-4]   = จำนวนก้าว (int32, little-endian)
// [5-8]   = แคลอรี่ (int32, /100 สำหรับ kcal)
// [9-12]  = ระยะทาง (int32, /100 สำหรับเมตร)
// [13-16] = ระยะเวลา (วินาที)
// [17-20] = เวลาที่ใช้งาน
// [21]    = อัตราการเต้นของหัวใจ (BPM) ← สำคัญ!
// [22-23] = อุณหภูมิ (raw/10 = °C)

void onDataReceived(List<int> data) {
  if (data[0] == 0x09) {
    final steps = _parseInt32(data, 1);
    final calories = _parseInt32(data, 5) / 100.0;
    final heartRate = data[21];  // BPM
    final temp = (data[22] + (data[23] << 8)) / 10.0;  // °C

    updateUI(steps: steps, hr: heartRate, temp: temp, cal: calories);
  }
}

// หยุดการสตรีม
await sendCommand(BleSDK.stopGo());

ตัวเลือกที่ 2: การวัดเมื่อต้องการ

การวัดประเภทระยะเวลาคำสั่ง
SpO2 (ออกซิเจนในเลือด)3~30 วินาที0x28
อัตราการเต้นของหัวใจ2~15 วินาที0x28
HRV1~60 วินาที0x28
อุณหภูมิ4~10 วินาที0x28
// เริ่มการวัด SpO2
await sendCommand(BleSDK.HealthMeasurementWithDataType(3, true));

// รอการเรียกกลับ (MeasurementOxygenCallback = "65")
// การตอบกลับมีเปอร์เซ็นต์ SpO2

// หยุดการวัด
await sendCommand(BleSDK.HealthMeasurementWithDataType(3, false));

การใช้งาน: การติดตามการนอน

😴 การตรวจจับและวิเคราะห์การนอนอัตโนมัติ
วิธีการทำงาน: อุปกรณ์ตรวจจับการนอนอัตโนมัติผ่านเซ็นเซอร์การเคลื่อนไหว + HR ไม่มีคำสั่งเพื่อ "เริ่ม" การติดตาม เก็บข้อมูล ~14 คืน (FIFO) ซิงค์ข้อมูลเมื่อผู้ใช้เปิดแอป

ค่าขั้นตอนการนอน

ค่าขั้นตอนสีที่แนะนำ
0ตื่น🟠 ส้ม
1นอนไม่ลึก🔵 น้ำเงินอ่อน
2นอนหลับลึก🟣 น้ำเงินเข้ม
3นอน REM🟣 ม่วง

ดึงข้อมูลการนอน

// คำสั่ง: 0x53 | โหมด: 0=ล่าสุด, 2=ต่อ, 99=ลบ
Future<List<SleepRecord>> fetchAllSleepData() async {
  List<Map> allRecords = [];
  bool hasMore = true;
  int mode = 0;  // เริ่มจากล่าสุด

  while (hasMore) {
    await sendCommand(BleSDK.GetDetailSleepDataWithMode(mode));
    final response = await waitForResponse(0x53);

    // โครงสร้างการตอบกลับ:
    // DataType: "26" (GetDetailSleepData)
    // End: true/false
    // Data: { date, arraySleepQuality, sleepUnitLength }

    allRecords.addAll(response['Data']);
    hasMore = response['End'] != true;
    mode = 2;  // โหมดต่อ
  }
  return allRecords.map((r) => SleepRecord.fromMap(r)).toList();
}

แยกวิเคราะห์และวิเคราะห์การนอน

class SleepRecord {
  final String date;           // "2025-12-10 23:30:00"
  final List<int> stages;     // [1, 1, 2, 2, 2, 1, 0, ...]
  final int unitMinutes;       // 5 หรือ 1 (ความยาวช่วงเวลา)

  // คุณสมบัติที่คำนวณ
  int get totalMinutes => stages.length * unitMinutes;
  String get duration => '${totalMinutes ~/ 60}ชม ${totalMinutes % 60}นาที';

  int get deepMinutes => stages.where((s) => s == 2 || s == 3).length * unitMinutes;
  int get lightMinutes => stages.where((s) => s == 1).length * unitMinutes;
  int get awakeMinutes => stages.where((s) => s == 0).length * unitMinutes;

  int get efficiency => totalMinutes > 0
      ? ((totalMinutes - awakeMinutes) / totalMinutes * 100).round()
      : 0;

  String get quality {
    if (efficiency >= 85) return 'ยอดเยี่ยม';
    if (efficiency >= 70) return 'ดี';
    if (efficiency >= 50) return 'ปานกลาง';
    return 'ไม่ดี';
  }
}

// สร้างการแสดงผลไทม์ไลน์การนอน
Widget buildSleepTimeline(List<int> stages) {
  return Row(
    children: stages.map((stage) => Expanded(
      child: Container(
        height: 20,
        color: _getStageColor(stage),
      ),
    )).toList(),
  );
}

Color _getStageColor(int stage) {
  switch (stage) {
    case 0: return Colors.orange;      // ตื่น
    case 1: return Colors.lightBlue;   // ไม่ลึก
    case 2: return Colors.indigo;      // ลึก
    case 3: return Colors.purple;      // REM
    default: return Colors.grey;
  }
}

โครงสร้างข้อมูลการตอบกลับ

รูปแบบไบต์ช่วงเวลาระยะเวลาสูงสุด
34 ไบต์[3-8]=วันที่, [9]=ความยาว, [10-33]=ขั้นตอน5 นาที~2 ชั่วโมง/ระเบียน
130 ไบต์[3-8]=วันที่, [9]=ความยาว, [10-129]=ขั้นตอน1 นาที~2 ชั่วโมง/ระเบียน

การใช้งาน: การติดตามการออกกำลังกายและกิจกรรม

🏃 โหมดหลายกีฬาและเซสชันการออกกำลังกาย

ประเภทโหมดกีฬา

โหมดชื่อโหมดชื่อโหมดชื่อ
0เดิน4ว่ายน้ำ8กระโดดเชือก
1วิ่ง5บาสเก็ตบอล9โยคะ
2ปั่นจักรยาน6ฟุตบอล10เครื่องเยียว
3เดินป่า7แบดมินตัน11พายเรือ

เริ่มเซสชันการออกกำลังกาย

// เริ่มโหมดการออกกำลังกาย
await sendCommand(BleSDK.EnterActivityMode(
  1,  // activityMode: 1=วิ่ง
  1   // workMode: 1=เริ่ม
));

// สำคัญ: ส่ง heartbeat ทุก 1 วินาทีเพื่อรักษาเซสชัน!
Timer.periodic(Duration(seconds: 1), (timer) async {
  if (!isExercising) {
    timer.cancel();
    return;
  }
  await sendCommand(BleSDK.sendHeartPackage(
    currentDistance,  // float: ระยะทางรวม
    elapsedSeconds,   // int: ระยะเวลา
    rssi              // int: ความแรงสัญญาณ
  ));
});

// อุปกรณ์ส่งการตอบกลับ 0x18 พร้อมอัตราการเต้นหัวใจแบบเรียลไทม์ระหว่างการออกกำลังกาย
void onDataReceived(List<int> data) {
  if (data[0] == 0x18) {
    final heartRate = data[1];
    final steps = _parseInt32(data, 2);
    final calories = _parseFloat(data, 6);
    final duration = _parseInt32(data, 10);
    updateWorkoutUI(hr: heartRate, steps: steps, cal: calories);
  }
}

// หยุดการออกกำลังกาย
await sendCommand(BleSDK.EnterActivityMode(1, 0));  // workMode: 0=หยุด

ดึงประวัติการออกกำลังกาย

// คำสั่ง: 0x5C | ระเบียน: 25 ไบต์แต่ละระเบียน
final records = await fetchHistory(BleSDK.GetActivityModeDataWithMode);

// ฟิลด์การตอบกลับต่อระเบียน:
// date, mode, modeName, heartRate, durationMinutes, steps, pace, calories, distance

records.forEach((r) {
  print('${r['date']}: ${r['modeName']} - ${r['durationMinutes']}นาที, '
        '${r['steps']} ก้าว, อัตราการเต้นหัวใจเฉลี่ย ${r['heartRate']}');
});
⚠️ จำเป็นต้องรักษาการเชื่อมต่อ: เมื่อสร้อยข้อมือเข้าสู่โหมดหลายกีฬาผ่านแอป, คุณต้องส่งแพ็กเก็ต heartbeat (0x17) ทุก 1 วินาที มิฉะนั้นสร้อยข้อมือจะออกจากโหมดกีฬาอัตโนมัติ

การใช้งาน: การซิงค์ข้อมูลประวัติ

📊 รูปแบบการดึงข้อมูลจำนวนมาก
การแบ่งหน้า: อุปกรณ์ส่งคืนสูงสุด ~50 ระเบียนต่อคำขอ ตรวจสอบ End: true/false ในการตอบกลับ ใช้โหมด 0x02 เพื่อต่อ

ประเภทประวัติที่มี

ข้อมูลรหัสเมธอด SDKระเบียนการเก็บ
จำนวนก้าวรายวัน0x51GetTotalActivityDataWithMode27 ไบต์~14 วัน
จำนวนก้าวรายละเอียด0x52GetDetailActivityDataWithMode25 ไบต์~7 วัน
การนอน0x53GetDetailSleepDataWithMode34/130 ไบต์~14 คืน
อัตราการเต้นหัวใจ0x54GetDynamicHRWithMode24 ไบต์~7 วัน
อัตราการเต้นหัวใจเดี่ยว0x55GetStaticHRWithMode10 ไบต์แตกต่างกัน
การทดสอบ HRV0x56GetHRVDataWithMode15 ไบต์แตกต่างกัน
การออกกำลังกาย0x5CGetActivityModeDataWithMode25 ไบต์~20 เซสชัน
ออกซิเจนในเลือด0x60GetBloodOxygen10 ไบต์แตกต่างกัน
อุณหภูมิ0x62GetTemperature_historyDataWithMode11 ไบต์แตกต่างกัน

รูปแบบการซิงค์ทั่วไป

// ฟังก์ชันดึงประวัติที่ใช้ซ้ำได้
Future<List<Map>> fetchHistory(Uint8List Function(int) cmdBuilder) async {
  List<Map> allRecords = [];
  bool hasMore = true;
  int mode = 0;  // 0=ล่าสุด, 2=ต่อ, 99=ลบ

  while (hasMore) {
    await sendCommand(cmdBuilder(mode));
    final response = await waitForResponse();

    if (response['Data'] != null) {
      allRecords.addAll(response['Data'] as List);
    }
    hasMore = response['End'] != true;
    mode = 2;  // ดึงข้อมูลต่อ

    await Future.delayed(Duration(milliseconds: 200));  // จำกัดอัตรา
  }
  return allRecords;
}

// ตัวอย่างการใช้งาน:
final sleepData = await fetchHistory(BleSDK.GetDetailSleepDataWithMode);
final hrData = await fetchHistory(BleSDK.GetDynamicHRWithMode);
final stepData = await fetchHistory(BleSDK.GetTotalActivityDataWithMode);
final workouts = await fetchHistory(BleSDK.GetActivityModeDataWithMode);

กลยุทธ์การซิงค์เบื้องหลัง

// กำหนดการซิงค์เป็นระยะด้วย WorkManager
await Workmanager().registerPeriodicTask(
  'health-sync',
  'syncHealthData',
  frequency: Duration(hours: 1),
  constraints: Constraints(
    networkType: NetworkType.connected,
    requiresBatteryNotLow: true,
  ),
);

// หรือซิงค์เวลานอน (03:00 น. รายวัน)
await Workmanager().registerOneOffTask(
  'sleep-sync',
  'syncSleepData',
  initialDelay: _getDelayUntil3AM(),
);

การใช้งาน: การแจ้งเตือนและการเตือน

🔔 ส่งการแจ้งเตือนไปยังอุปกรณ์

ประเภทการแจ้งเตือน

ประเภทค่าประเภทค่า
สายโทรศัพท์0Twitter7
WeChat1VK8
SMS2WhatsApp9
Facebook3หยุดสายโทร0xFF
Telegram4
// ส่งการแจ้งเตือนไปยังอุปกรณ์ (สูงสุด 60 ไบต์ข้อความ)
await sendCommand(BleSDK.setNotifyData(Notifier(
  type: Notifier.Data_WhatApp,  // 9
  info: 'ข้อความใหม่จาก John'
)));

// การแจ้งเตือนสายโทรเข้า
await sendCommand(BleSDK.setNotifyData(Notifier(
  type: Notifier.Data_Tel,  // 0
  info: 'แม่'
)));

// หยุดสายโทรเข้า (ผู้ใช้ปฏิเสธ/รับ)
await sendCommand(BleSDK.setNotifyData(Notifier(
  type: Notifier.Data_Stop_Tel,  // 0xFF
  info: ''
)));

// กระตุ้นการสั่น (เช่น สำหรับการเตือนแบบกำหนดเอง)
await sendCommand(BleSDK.MotorVibrationWithTimes(3));  // สั่น 3 ครั้ง

การใช้งาน: การตั้งค่าอุปกรณ์

⚙️ การปลุก, เป้าหมาย, การตรวจสอบอัตโนมัติ

ตั้งค่าการปลุก

// ตั้งค่าการปลุกได้สูงสุด 5 รายการ
final alarms = [
  Clock(
    number: 0,
    type: 1,           // 1=ปกติ, 2=ยา, 3=น้ำ
    hour: 7, minute: 30,
    weeks: ['0','1','1','1','1','1','0'],  // จันทร์-ศุกร์
    content: 'ตื่นนอน',
    enable: true,
  ),
];
await sendCommand(BleSDK.setClockData(alarms));

// อ่านการปลุก
await sendCommand(BleSDK.GetAlarmClock(0));  // 0=อ่าน, 99=ลบทั้งหมด

// ลบการปลุกทั้งหมด
await sendCommand(BleSDK.deleteAllClock());

ตั้งค่าเป้าหมายจำนวนก้าว

await sendCommand(BleSDK.SetStepGoal(10000));  // 10,000 ก้าว
final goal = await sendCommand(BleSDK.GetStepGoal());

การตรวจสอบอัตราการเต้นหัวใจอัตโนมัติ

// กำหนดการวัดอัตราการเต้นหัวใจอัตโนมัติ
await sendCommand(BleSDK.SetAutomaticHRMonitoring(MyAutomaticHRMonitoring(
  open: 2,           // 0=ปิด, 1=ต่อเนื่อง, 2=ช่วงเวลา
  startHour: 8, startMinute: 0,
  endHour: 22, endMinute: 0,
  week: 127,         // ทุกวัน (pow(2,0)+pow(2,1)+...+pow(2,6))
  time: 30,          // ทุก 30 นาที
  type: 1,           // 1=HR, 2=SpO2, 3=Temp, 4=HRV
)));

// อ่านการตั้งค่าปัจจุบัน
await sendCommand(BleSDK.GetAutomaticHRMonitoring(1));  // type=1 สำหรับ HR

การเตือนการนั่งเป็นเวลานาน

await sendCommand(BleSDK.SetSedentaryReminder(MySedentaryReminder(
  startHour: 9, startMinute: 0,
  endHour: 18, endMinute: 0,
  week: 62,          // จันทร์-ศุกร์ (pow(2,1)+...+pow(2,5))
  intervalTime: 60,  // เตือนทุก 60 นาที
  leastStep: 100,    // ข้ามถ้าผู้ใช้เดิน 100+ ก้าว
  enable: true,
)));

รายละเอียดโปรโตคอล

โครงสร้างแพ็กเก็ต

┌──────┬──────────────────────────────┬─────┐
│ [0]  │ [1] [2] [3] ... [13] [14]   │[15] │
│ CMD  │       Payload (14 bytes)    │ CRC │
└──────┴──────────────────────────────┴─────┘

การคำนวณ CRC:
int crc = 0;
for (int i = 0; i < 15; i++) crc += packet[i];
packet[15] = crc & 0xFF;

การเข้ารหัส BCD (สำหรับค่าเวลา)

// Decimal to BCD: 23 → 0x23
int toBcd(int v) => int.parse(v.toString(), radix: 16);

// BCD to Decimal: 0x23 → 23
int fromBcd(int b) => ((b >> 4) * 10) + (b & 0x0F);

// ตัวอย่างแพ็กเก็ตเวลา: 2024-12-11 16:30:45
// [0x01, 0x24, 0x12, 0x11, 0x16, 0x30, 0x45, 0, 0, 0, 0, 0, 0, 0, 0, CRC]

การแยกวิเคราะห์จำนวนเต็ม Little-Endian

int parseInt32(List<int> data, int offset) {
  return data[offset] |
         (data[offset+1] << 8) |
         (data[offset+2] << 16) |
         (data[offset+3] << 24);
}

// ตัวอย่าง: [0x10, 0x27, 0x00, 0x00] ที่ offset 1
// = 0x10 + 0x2700 = 10000

การตรวจจับท้ายประวัติ

bool isEndOfData(List<int> data) {
  return data.length >= 2 &&
         data[data.length - 1] == 0xFF &&
         data[data.length - 2] == data[0];  // CMD + 0xFF
}
// ตัวอย่าง: ท้ายข้อมูลการนอน = [..., 0x53, 0xFF]

คู่มือคำสั่งทั้งหมด

คำสั่งรหัสเมธอด SDKประเภท
ตั้งเวลา0x01SetDeviceTime()การตั้งค่า
ตั้งข้อมูลผู้ใช้0x02SetPersonalInfo()การตั้งค่า
ตั้งข้อมูลอุปกรณ์0x03SetDeviceInfo()การตั้งค่า
อ่านข้อมูลอุปกรณ์0x04GetDeviceInfo()การตั้งค่า
เปิดกิจกรรม0x09RealTimeStep()สตรีม
ตั้งเป้าหมาย0x0BSetStepGoal()การตั้งค่า
รีเซ็ตโรงงาน0x12Reset()อันตราย
อ่านแบตเตอรี่0x13GetDeviceBatteryLevel()การตั้งค่า
สภาพอากาศ0x15WeatherDataส่ง
แพ็กเก็ตหัวใจ0x17sendHeartPackage()สตรีม
ข้อมูลการออกกำลังกาย0x18อุปกรณ์ส่งสตรีม
เริ่มการออกกำลังกาย0x19EnterActivityMode()สตรีม
อ่าน MAC0x22GetDeviceMacAddress()การตั้งค่า
ตั้งนาฬิกาปลุก0x23setClockData()การตั้งค่า
ตั้งการเตือนนั่งนาน0x25SetSedentaryReminder()การตั้งค่า
อ่านการเตือนนั่งนาน0x26GetSedentaryReminder()การตั้งค่า
อ่านเวอร์ชัน0x27GetDeviceVersion()การตั้งค่า
การวัด0x28HealthMeasurementWithDataType()สตรีม
ตั้ง HR อัตโนมัติ0x2ASetAutomaticHRMonitoring()การตั้งค่า
อ่าน HR อัตโนมัติ0x2BGetAutomaticHRMonitoring()การตั้งค่า
รีเซ็ต MCU0x2EMCUReset()อันตราย
การสั่น0x36MotorVibrationWithTimes()ส่ง
อ่านชื่อ0x3EGetDeviceName()การตั้งค่า
อ่านเวลา0x41GetDeviceTime()การตั้งค่า
อ่านข้อมูลผู้ใช้0x42GetPersonalInfo()การตั้งค่า
เข้า OTA0x47enterOTA()อันตราย
อ่านเป้าหมาย0x4BGetStepGoal()การตั้งค่า
แจ้งเตือน0x4DsetNotifyData()ส่ง
อ่านจำนวนก้าวรวม0x51GetTotalActivityDataWithMode()ประวัติ
อ่านจำนวนก้าวละเอียด0x52GetDetailActivityDataWithMode()ประวัติ
อ่านการนอน0x53GetDetailSleepDataWithMode()ประวัติ
อ่าน HR ประวัติ0x54GetDynamicHRWithMode()ประวัติ
อ่าน HR เดี่ยว0x55GetStaticHRWithMode()ประวัติ
อ่าน HRV0x56GetHRVDataWithMode()ประวัติ
อ่านนาฬิกาปลุก0x57GetAlarmClock()การตั้งค่า
อ่าน GPS0x5AGetActivityModeDataWithMode()ประวัติ
อ่านการออกกำลังกาย0x5CGetActivityModeDataWithMode()ประวัติ
อ่าน O2 ในเลือด0x60GetBloodOxygen()ประวัติ
อ่านอุณหภูมิประวัติ0x62GetTemperature_historyDataWithMode()ประวัติ
อ่าน SpO2 อัตโนมัติ0x66GetAutoBloodOxygen()ประวัติ

ค่าคงที่การตอบกลับ

ค่า DataType (BleConst)

GetDeviceTime"0"
SetDeviceTime"1"
GetPersonalInfo"2"
GetDeviceInfo"4"
GetDeviceBatteryLevel"9"
GetDeviceMacAddress"10"
GetDeviceVersion"11"
GetDeviceName"15"
GetAlarmClock"18"
RealTimeStep"23"
GetTotalActivityData"24"
GetDetailSleepData"26"
GetDynamicHR"27"
GetActivityModeData"29"
EnterActivityMode"30"
GetHRVData"42"
Blood_oxygen"55"
MeasurementOxygenCallback"65"

คีย์ฟิลด์ข้อมูล (DeviceKey)

กิจกรรม
Stepจำนวนก้าว
Calorieskcal
Distanceเมตร
HeartRateBPM
TempData°C
การนอน
ArraySleepอาร์เรย์ขั้นตอน
sleepUnitLength1 หรือ 5 นาที
สุขภาพ
Blood_oxygenSpO2 %
HRVความแปรผัน
Stressระดับความเครียด
อุปกรณ์
BatteryLevel0-100%
MacAddressสตริงเลขฐานสิบหก
DeviceVersionเฟิร์มแวร์