Core Bluetooth คือเฟรมเวิร์กของ Apple สำหรับการโต้ตอบกับ Bluetooth Low Energy บน iOS, iPadOS และ macOS เฟรมเวิร์กนี้มีชุด API ที่สมบูรณ์สำหรับการทำงานในบทบาท BLE ทั้งสองแบบ: อุปกรณ์ศูนย์กลาง (CBCentralManager) สำหรับการสแกนและเชื่อมต่อกับอุปกรณ์รอบข้าง และอุปกรณ์รอบข้าง (CBPeripheralManager) สำหรับจำลองเซิร์ฟเวอร์ BLE Core Bluetooth ทำการแยกสแต็กโปรโตคอล BLE จากวิทยุทางกายภาพไปยังโปรไฟล์ GATT ระดับแอปพลิเคชัน ตาม Apple Developer, 2026 Core Bluetooth เป็น API ทางการเดียวของ Apple สำหรับการพัฒนา BLE รองรับ BLE 4.0–5.4 พร้อม extended advertising, 2M PHY และ LE Audio
ประเด็นสำคัญ
Core Bluetooth แบ่งสแต็ก BLE ออกเป็นสองบทบาทเชิงตรรกะที่กำหนดโดยข้อกำหนด Bluetooth SIG บทบาทอุปกรณ์ศูนย์กลาง (Central) แสดงโดยคลาส CBCentralManager — เริ่มต้นการสแกน สร้างการเชื่อมต่อ และจัดการรายการของ CBPeripheral ที่เชื่อมต่อ บทบาทอุปกรณ์รอบข้าง (Peripheral) แสดงโดย CBPeripheralManager — เผยแพร่บริการและคุณลักษณะ ตอบสนองต่อคำขอจากศูนย์กลาง และส่งการแจ้งเตือน หนึ่งเซสชัน iOS สามารถทำงานพร้อมกันในทั้งสองบทบาทบนวิทยุ BLE ที่แตกต่างกัน แต่แอปพลิเคชันทั่วไปใช้บทบาทเดียว
สถาปัตยกรรม Core Bluetooth ประกอบด้วยนามธรรมสำคัญห้าประการ CBCentralManager จัดการสถานะของอะแดปเตอร์ Bluetooth ของอุปกรณ์: poweredOn (พร้อมทำงาน), poweredOff (Bluetooth ปิดใช้งาน), unauthorized (ไม่มีสิทธิ์), unsupported (BLE ไม่พร้อมใช้งาน) CBPeripheral แสดงถึงอุปกรณ์ BLE ระยะไกลด้วย UUID, ชื่อ, RSSI และลำดับชั้น GATT CBService — กลุ่มตรรกะของคุณลักษณะ CBCharacteristic — จุดข้อมูลสำหรับการอ่าน/เขียน/การแจ้งเตือน CBPeripheralManager สร้างเซิร์ฟเวอร์ GATT เฉพาะที่สำหรับจำลองอุปกรณ์รอบข้าง
| คลาส | บทบาท | เมธอดหลัก |
|---|---|---|
| CBCentralManager | อุปกรณ์ศูนย์กลาง | scanForPeripherals, connect, cancelPeripheralConnection, retrievePeripherals |
| CBPeripheral | อุปกรณ์รอบข้างระยะไกล | discoverServices, discoverCharacteristics, readValue, writeValue, setNotifyValue |
| CBPeripheralManager | อุปกรณ์รอบข้างเฉพาะที่ | addService, removeService, startAdvertising, respondToRequest, updateValue |
| CBCentral | ศูนย์กลางระยะไกล | maximumUpdateValueLength, identifier, ancsAuthorized |
สถานะของ CBCentralManager ควบคุมการดำเนินการ BLE ทั้งหมด เมื่อแอปเริ่มทำงาน centralManagerDidUpdateState จะถูกเรียกด้วยสถานะ Bluetooth ปัจจุบัน หากสถานะไม่ใช่ .poweredOn การเรียก BLE ใดๆ จะถูกละเว้นโดยระบบ นักพัฒนาต้องตรวจสอบสถานะก่อนการสแกนและการเชื่อมต่อแต่ละครั้ง การเปลี่ยนจาก .poweredOff เป็น .poweredOn เกิดขึ้นเมื่อเปิด Bluetooth ในการตั้งค่า iOS — ตัวแทนได้รับการเรียกซ้ำและแอปสามารถดำเนินการสแกนต่อได้
CBCentralManager คือจุดเริ่มต้นสำหรับการดำเนินการ BLE ทั้งหมดจากฝั่งอุปกรณ์ศูนย์กลาง การเริ่มต้นรับตัวแทน (CBCentralManagerDelegate) และ DispatchQueue — Apple แนะนำให้ใช้คิวหลักเพื่อความเรียบง่ายหรือคิวแบบอนุกรมเพื่อประสิทธิภาพ หลังจากการเริ่มต้น เฟรมเวิร์กจะตรวจสอบสถานะ Bluetooth โดยอัตโนมัติและเรียก centralManagerDidUpdateState: — ตัวแทนบังคับตัวแรกที่ต้องจัดการ
การสแกนเริ่มต้นด้วยเมธอด scanForPeripheralsWithServices:options: พารามิเตอร์แรกคืออาร์เรย์ของ CBUUID บริการสำหรับการกรอง: หากรู้จัก UUID ของบริการที่ต้องการ การส่งผ่านจะลดการใช้พลังงานและเวลาในการค้นหา หากเป็น nil อุปกรณ์ BLE ทั้งหมดในระยะจะถูกค้นพบ ตัวเลือกรวมถึง .allowDuplicatesKey (การค้นพบซ้ำของอุปกรณ์เดียวกัน) และ .solicitedServiceUUIDsKey (สำหรับบริการที่เผยแพร่บนศูนย์กลาง)
import CoreBluetooth
class BLECentral: NSObject {
private var centralManager: CBCentralManager!
private var discoveredPeripherals: [CBPeripheral] = []
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: .main)
}
// เริ่มการสแกน BLE
func startScan() {
guard centralManager.state == .poweredOn else {
print("Bluetooth ไม่พร้อมใช้งาน")
return
}
// สแกนอุปกรณ์ทั้งหมด (nil = ไม่มีการกรอง)
centralManager.scanForPeripherals(withServices: nil,
options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
}
// หยุดการสแกน
func stopScan() {
centralManager.stopScan()
}
// เชื่อมต่อกับอุปกรณ์ที่เลือก
func connect(to peripheral: CBPeripheral) {
centralManager.connect(peripheral, options: nil)
}
}
// MARK: - CBCentralManagerDelegate
extension BLECentral: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
startScan()
}
}
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi: NSNumber) {
if !discoveredPeripherals.contains(where: { $0.identifier == peripheral.identifier }) {
discoveredPeripherals.append(peripheral)
print("Found devices: \(peripheral.name ?? "Unknown"), RSSI: \(rssi)")
}
}
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
print("Connected: \(peripheral.identifier)")
peripheral.delegate = self
peripheral.discoverServices(nil)
}
func centralManager(_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: Error?) {
print("Disconnected: \(peripheral.identifier)")
}
}
คลาส BLECentral แสดงวงจรเต็มรูปแบบของการสแกนและเชื่อมต่ออุปกรณ์ BLE centralManagerDidUpdateState เริ่มการสแกนเมื่อ Bluetooth เปิดใช้งาน didDiscoverPeripheral รวบรวมอุปกรณ์ที่พบในอาร์เรย์ discoveredPeripherals ด้วยการกำจัดรายการซ้ำตาม identifier หลังจากการเชื่อมต่อ (didConnect) การค้นพบบริการจะเริ่มทันที — นี่คือขั้นตอนบังคับก่อนการดำเนินการ GATT ใดๆ
CBPeripheralManager คือคลาสสำหรับจำลองอุปกรณ์รอบข้าง BLE บน iOS แอปในบทบาทรอบข้างสามารถเผยแพร่บริการและคุณลักษณะของตน ยอมรับคำขออ่าน/เขียนที่เข้ามาจากอุปกรณ์ศูนย์กลาง และส่งการแจ้งเตือน CBPeripheralManager ใช้สำหรับอุปกรณ์เสริม BLE ที่จำลองโดย iPhone: รีโมท คีย์บอร์ด ตัวติดตาม เกตเวย์ IoT
วงจรชีวิตของ CBPeripheralManager เริ่มต้นด้วยการเริ่มต้นและตัวแทน CBPeripheralManagerDelegate หลังจากได้รับการยืนยัน poweredOn ผ่าน peripheralManagerDidUpdateState: บริการจะถูกเผยแพร่ (addService:) และการโฆษณาจะเริ่มขึ้น (startAdvertising:) ข้อมูลโฆษณา CBAdvertisementData รวมถึงชื่อเฉพาะที่ (CBAdvertisementDataLocalNameKey), UUID บริการ (CBAdvertisementDataServiceUUIDsKey) และระดับกำลังส่ง (CBAdvertisementDataTxPowerLevelKey) ขนาดแพ็กเก็ตโฆษณาสูงสุดคือ 31 ไบต์สำหรับ BLE 4.0, 251 ไบต์สำหรับ extended advertising BLE 5.0+
// อุปกรณ์รอบข้าง BLE บน iOS ผ่าน CBPeripheralManager
class BLEPeripheral: NSObject {
private var peripheralManager: CBPeripheralManager!
let serviceUUID = CBUUID(string: "1234")
let characteristicUUID = CBUUID(string: "5678")
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: .main)
}
// เผยแพร่บริการพร้อมคุณลักษณะ
func setupService() {
let characteristic = CBMutableCharacteristic(
type: characteristicUUID,
properties: [.read, .write, .notify],
value: nil,
permissions: [.readable, .writeable]
)
let service = CBMutableService(type: serviceUUID, primary: true)
service.characteristics = [characteristic]
peripheralManager.add(service)
}
// เริ่มโฆษณา
func startAdvertising() {
let advertisementData: [String: Any] = [
CBAdvertisementDataLocalNameKey: "My BLE Device",
CBAdvertisementDataServiceUUIDsKey: [serviceUUID]
]
peripheralManager.startAdvertising(advertisementData)
}
}
// MARK: - CBPeripheralManagerDelegate
extension BLEPeripheral: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
setupService()
}
}
func peripheralManager(_ peripheral: CBPeripheralManager,
didAdd service: CBService,
error: Error?) {
if error == nil {
startAdvertising()
}
}
// จัดการคำขออ่าน
func peripheralManager(_ peripheral: CBPeripheralManager,
didReceiveRead request: CBATTRequest) {
let data = "CurrentValue".data(using: .utf8)!
request.value = data
peripheralManager.respond(to: request, withResult: .success)
}
// จัดการคำขอเขียน
func peripheralManager(_ peripheral: CBPeripheralManager,
didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
if let value = request.value {
print("Write: \(value)")
}
}
peripheralManager.respond(to: requests.first!, withResult: .success)
}
}
คลาส BLEPeripheral สร้างเซิร์ฟเวอร์ BLE ด้วยคุณลักษณะเดียวที่รองรับการอ่าน การเขียน และการแจ้งเตือน หลังจากการเริ่มต้น peripheralManagerDidUpdateState เผยแพร่บริการผ่าน addService: จากนั้นเริ่มโฆษณาผ่าน startAdvertising: ตัวจัดการ didReceiveRead และ didReceiveWrite ตอบสนองต่อคำขอ GATT ที่เข้ามาจากอุปกรณ์ศูนย์กลาง เมธอด updateValue:forCharacteristic:onSubscribedCentrals: ใช้สำหรับส่งการแจ้งเตือน
การดำเนินการ GATT (Generic Attribute Profile) เป็นพื้นฐานของการแลกเปลี่ยนข้อมูลใน Core Bluetooth หลังจากการค้นพบบริการและคุณลักษณะ อุปกรณ์ศูนย์กลางสามารถดำเนินการสามประเภท: อ่านค่าคุณลักษณะ เขียนค่า และสมัครรับการแจ้งเตือน/การบอกกล่าว การดำเนินการแต่ละครั้งเป็นแบบอะซิงโครนัสและส่งคืนผลลัพธ์ผ่านการเรียกกลับ CBPeripheralDelegate ที่เกี่ยวข้อง
การอ่าน: ดำเนินการโดยเรียก readValueForCharacteristic: ค่ามาถึงใน peripheral:didUpdateValueForCharacteristic:error: สำคัญ: การอ่านส่งคืนค่าปัจจุบันจากอุปกรณ์ ไม่ใช่ค่าที่แคชไว้ หากอุปกรณ์ไม่รองรับการอ่าน (คุณสมบัติ .read) การเรียกจะส่งคืนข้อผิดพลาด สำหรับค่าขนาดใหญ่ (ใหญ่กว่า MTU) BLE จะแยกส่วนและประกอบข้อมูลใหม่โดยอัตโนมัติที่ระดับ GATT
การเขียน: ดำเนินการโดยเรียก writeValue:forCharacteristic:type: BLE รองรับสองรูปแบบการเขียน: withResponse (เชื่อถือได้ พร้อมการยืนยัน) และ withoutResponse (รวดเร็ว ไม่มีการยืนยัน) คุณสมบัติ CBCharacteristic.properties กำหนดประเภทการเขียนที่พร้อมใช้งาน ขนาดสูงสุดของแพ็กเก็ตเขียนเดียวถูกจำกัดโดย MTU: 23 ไบต์สำหรับ BLE 4.0 (20 ไบต์เพย์โหลด + 3 ไบต์ส่วนหัว) สูงสุด 247 ไบต์สำหรับ BLE 5.0 พร้อม MTU แบบขยาย (MTU 251)
การแจ้งเตือน: เปิดใช้งานโดยเรียก setNotifyValue:true forCharacteristic: หลังจากการสมัครสมาชิก อุปกรณ์รอบข้างจะส่งการอัปเดตโดยอัตโนมัติผ่าน peripheral:didUpdateValueForCharacteristic: ทุกครั้งที่ค่าคุณลักษณะเปลี่ยนแปลง หากต้องการปิดการแจ้งเตือน ให้เรียก setNotifyValue:false forCharacteristic: Core Bluetooth จัดการตัวอธิบาย CCCD บนอุปกรณ์รอบข้างโดยอัตโนมัติ
| การดำเนินการ | เมธอด | ตัวแทน | ประเภทการถ่ายโอน |
|---|---|---|---|
| การอ่าน | readValueForCharacteristic: | didUpdateValueForCharacteristic | การสอบถาม (คำขอ-ตอบกลับ) |
| การเขียน withResponse | writeValue:forCharacteristic:type:withResponse | didWriteValueForCharacteristic | พร้อมการยืนยัน |
| การเขียน withoutResponse | writeValue:forCharacteristic:type:withoutResponse | ไม่มีตัวแทน | ไม่มีการยืนยัน |
| การแจ้งเตือน | setNotifyValue:true forCharacteristic: | didUpdateNotificationStateForCharacteristic + didUpdateValueForCharacteristic | พุชจากอุปกรณ์รอบข้าง |
โหมดพื้นหลัง ใน Core Bluetooth ช่วยให้แอป BLE สามารถสแกนต่อ รักษาการเชื่อมต่อ และรับการแจ้งเตือนขณะอยู่ในพื้นหลัง ในการเปิดใช้งาน ให้เปิดความสามารถ “Uses Bluetooth LE accessories” ใน Xcode (Info.plist → Required background modes → App communicates using Core Bluetooth) และเพิ่มคีย์ “bluetooth-central” ใน UIBackgroundModes สำหรับบทบาทรอบข้าง — “bluetooth-peripheral”
State Restoration เป็นกลไกของ Core Bluetooth สำหรับการคืนค่าสถานะของการเชื่อมต่อ BLE หลังจากที่ iOS รีสตาร์ทแอป เมื่อโหมดพื้นหลังทำงานและมีการระบุ restoreIdentifier ในการเริ่มต้น CBCentralManager หรือ CBPeripheralManager iOS จะบันทึกสถานะสแต็ก BLE เมื่อแอปสิ้นสุดและคืนค่าเมื่อเปิดใช้ครั้งถัดไป ตัวแทน centralManager:willRestoreState: รับพจนานุกรมพร้อม CBPeripheral ที่บันทึกไว้และการเชื่อมต่อที่รอดำเนินการ
// การกำหนดค่า Core Bluetooth พร้อม State Restoration
class BLECentralWithRestoration: NSObject {
let restoreIdentifier = "com.app.blecentral"
private var centralManager: CBCentralManager!
override init() {
super.init()
let options: [String: Any] = [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdentifier,
CBCentralManagerOptionShowPowerAlertKey: true
]
centralManager = CBCentralManager(delegate: self,
queue: nil,
options: options)
}
}
extension BLECentralWithRestoration: CBCentralManagerDelegate {
// คืนค่าสถานะหลังจากรีสตาร์ท
func centralManager(_ central: CBCentralManager,
willRestoreState dict: [String : Any]) {
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
as? [CBPeripheral] {
for peripheral in peripherals {
peripheral.delegate = self
// คืนค่าการค้นพบ GATT
peripheral.discoverServices(nil)
}
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Bluetooth พร้อมหลังจากการคืนค่า")
}
}
}
ในการกำหนดค่า BLECentralWithRestoration คีย์ CBCentralManagerOptionRestoreIdentifierKey เปิดใช้งานการบันทึกสถานะ หากแอปถูก iOS ยุติ (เช่น เนื่องจากหน่วยความจำไม่เพียงพอ) ในการเปิดใช้ครั้งถัดไป centralManager:willRestoreState: จะได้รับรายการของ CBPeripheral ที่เชื่อมต่อก่อนหน้านี้ แอปจะคืนค่าตัวแทนและดำเนินการค้นพบบริการอีกครั้ง — ผู้ใช้ไม่สังเกตเห็นการขัดจังหวะการเชื่อมต่อ หากไม่มี State Restoration เซสชัน BLE ทั้งหมดจะหายไปเมื่อแอปสิ้นสุด
ตัวอย่างที่สมบูรณ์ ของแอป BLE ใน Swift รวมอุปกรณ์ศูนย์กลางและรอบข้างไว้ในโปรเจกต์เดียว แอปสามารถทำงานในสองโหมด: ค้นพบและเชื่อมต่อกับอุปกรณ์ BLE (ศูนย์กลาง) หรือจำลองอุปกรณ์เสริม BLE (รอบข้าง) ด้านล่างนี้คือสถาปัตยกรรมพร้อมตัวจัดการ BLE ทั่วไปที่เลือกบทบาทเมื่อเริ่มต้น
// ตัวจัดการ BLE สากลสำหรับศูนย์กลางและรอบข้าง
class BLEManager {
enum Role {
case central
case peripheral
}
private let role: Role
private var centralManager: CBCentralManager?
private var peripheralManager: CBPeripheralManager?
let advertisedServiceUUID = CBUUID(string: "A001")
init(role: Role) {
self.role = role
switch role {
case .central:
centralManager = CBCentralManager(delegate: nil, queue: .main)
case .peripheral:
peripheralManager = CBPeripheralManager(delegate: nil, queue: .main)
}
}
// อุปกรณ์ศูนย์กลาง: การสแกน
func scanForDevices() {
centralManager?.scanForPeripherals(withServices: nil, options: nil)
}
// อุปกรณ์รอบข้าง: การโฆษณา
func advertiseService() {
let data: [String: Any] = [
CBAdvertisementDataServiceUUIDsKey: [advertisedServiceUUID]
]
peripheralManager?.startAdvertising(data)
}
}
// การใช้งานเมื่อเริ่มต้น
let isCentral = UserDefaults.standard.bool(forKey: "isCentral")
let manager = BLEManager(role: isCentral ? .central : .peripheral)
if isCentral {
manager.scanForDevices()
} else {
manager.advertiseService()
}
BLEManager เลือกบทบาทเมื่อเริ่มต้นและสร้างตัวจัดการที่เกี่ยวข้อง (CBCentralManager หรือ CBPeripheralManager) แฟล็กบทบาทสามารถเก็บไว้ใน UserDefaults หรือส่งผ่านเซิร์ฟเวอร์การกำหนดค่า วิธีการนี้ช่วยให้แอป BLE ปรับตัวตามกรณีการใช้งาน: ณ จุดขาย iPhone ทำงานเป็นศูนย์กลางสำหรับสแกนเครื่องชำระเงิน บนเกตเวย์ IoT — เป็นอุปกรณ์รอบข้างสำหรับรวบรวมข้อมูลจากเซ็นเซอร์
คำถามที่พบบ่อย
Core Bluetooth คือเฟรมเวิร์กของ Apple สำหรับการพัฒนา BLE บน iOS, iPadOS และ macOS ให้ API สำหรับทั้งอุปกรณ์ศูนย์กลาง (CBCentralManager) และอุปกรณ์รอบข้าง (CBPeripheralManager) รองรับ BLE 4.0–5.4, extended advertising, 2M PHY และ LE Audio Core Bluetooth เป็น API ทางการเดียวของ Apple สำหรับการสื่อสาร BLE ซึ่งจำเป็นสำหรับแอป iOS ทั้งหมดที่ทำงานกับ Bluetooth Low Energy
CBCentralManager คือคลาสสำหรับทำงานในบทบาทอุปกรณ์ศูนย์กลาง: สแกนอุปกรณ์รอบข้าง BLE สร้างการเชื่อมต่อ อ่านและเขียนคุณลักษณะ CBPeripheralManager คือคลาสสำหรับทำงานในบทบาทรอบข้าง: เผยแพร่บริการ ตอบสนองต่อคำขออ่าน/เขียน และส่งการแจ้งเตือน iPhone เครื่องเดียวสามารถทำงานในทั้งสองบทบาทพร้อมกันผ่านอินสแตนซ์ตัวจัดการที่แตกต่างกัน
สำหรับการทำงาน BLE ในพื้นหลัง ให้เปิดความสามารถ “Uses Bluetooth LE accessories” ใน Xcode และเพิ่มคีย์ “bluetooth-central” ใน UIBackgroundModes สำหรับบทบาทรอบข้าง — “bluetooth-peripheral” ระบุ restoreIdentifier เมื่อเริ่มต้นตัวจัดการสำหรับ State Restoration หากไม่มีการตั้งค่าเหล่านี้ แอปในพื้นหลังจะไม่ได้รับเหตุการณ์ BLE และจะสูญเสียการเชื่อมต่อ
สาเหตุทั่วไป: CBCentralManager.state != .poweredOn (Bluetooth ปิดหรือไม่ได้รับอนุญาต), ไม่ได้ตั้งค่าตัวแทน, อุปกรณ์อยู่นอกระยะหรือไม่ส่งแพ็กเก็ตโฆษณา ตรวจสอบสิทธิ์ NSBluetoothAlwaysUsageDescription ใน Info.plist, สถานะ Bluetooth ใน centralManagerDidUpdateState และตรวจสอบให้แน่ใจว่า scanForPeripherals ถูกเรียกเมื่อ .poweredOn เท่านั้น
ได้ Core Bluetooth รองรับการเชื่อมต่อพร้อมกันกับอุปกรณ์ BLE หลายตัว แต่ละ CBPeripheral ถูกจัดการอย่างอิสระผ่านตัวแทนของตัวเอง iOS จำกัดจำนวนการเชื่อมต่อ BLE พร้อมกันในระดับระบบ (โดยปกติ 5–7 สำหรับ iPhone) สำหรับสถานการณ์ 1:N (เช่น ศูนย์ออกกำลังกายที่มีตัวติดตาม 10 ตัว) จำเป็นต้องจัดคิวและให้บริการอุปกรณ์รอบข้างแบบวนรอบ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม