Web Bluetooth API Integration

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

Web Bluetooth API Integration on Website

Web Bluetooth API allows the browser to communicate directly with Bluetooth Low Energy (BLE) devices without a mobile app. User opens a web page, clicks "Connect", selects a device from the list — and the site starts reading sensor data, controlling a lamp, or sending commands to a trainer.

Support and Limitations

Supported: Chrome 70+, Edge 79+, Chrome Android 56+. Not available: Firefox (frozen), Safari/iOS (blocked by Apple policy).

HTTPS only. Only after user gesture — requestDevice() call must be inside a click handler. Web Workers not supported.

BLE Architecture

BLE device contains ServicesCharacteristics. For example, "Heart Rate" service (UUID 0x180D) contains "Heart Rate Measurement" characteristic (UUID 0x2A37). Standard service UUIDs are defined by Bluetooth SIG, custom ones are 128-bit manufacturer UUIDs.

Connecting to Device

interface BluetoothDevice {
  name: string
  gatt: BluetoothRemoteGATTServer
}

async function connectToDevice(serviceUUID: string): Promise<BluetoothRemoteGATTServer> {
  const device = await navigator.bluetooth.requestDevice({
    filters: [
      { services: [serviceUUID] },
      // Or search by name:
      // { namePrefix: 'Mi Band' },
    ],
    optionalServices: [
      'battery_service',  // Standard UUID
      '0x180A',           // Device Information
    ],
  })

  console.log(`Connecting to: ${device.name}`)

  device.addEventListener('gattserverdisconnected', () => {
    console.log('Device disconnected')
    // Attempt reconnect here
  })

  const server = await device.gatt!.connect()
  return server
}

Reading Data: Heart Rate Monitor

class HeartRateMonitor {
  private server: BluetoothRemoteGATTServer | null = null
  private characteristic: BluetoothRemoteGATTCharacteristic | null = null

  async connect() {
    this.server = await connectToDevice('heart_rate')

    const service = await this.server.getPrimaryService('heart_rate')
    this.characteristic = await service.getCharacteristic('heart_rate_measurement')

    // Subscribe to notifications (data arrives automatically)
    await this.characteristic.startNotifications()

    this.characteristic.addEventListener(
      'characteristicvaluechanged',
      this.handleHeartRateMeasurement.bind(this)
    )
  }

  private handleHeartRateMeasurement(event: Event) {
    const value = (event.target as BluetoothRemoteGATTCharacteristic).value!
    const flags = value.getUint8(0)

    let heartRate: number
    if (flags & 0x01) {
      // 16-bit format
      heartRate = value.getUint16(1, true)
    } else {
      // 8-bit format
      heartRate = value.getUint8(1)
    }

    // RR-intervals (distance between beats in ms) — optional
    const rrIntervals: number[] = []
    if (flags & 0x10) {
      for (let i = 2; i + 1 < value.byteLength; i += 2) {
        rrIntervals.push(value.getUint16(i, true) / 1024 * 1000)
      }
    }

    console.log(`Heart Rate: ${heartRate} bpm, RR: [${rrIntervals.join(', ')}] ms`)
  }

  async disconnect() {
    await this.characteristic?.stopNotifications()
    this.server?.disconnect()
  }
}

Writing Data: Smart Light Control

class SmartLightController {
  private server: BluetoothRemoteGATTServer | null = null
  private controlChar: BluetoothRemoteGATTCharacteristic | null = null

  // Custom service UUID (example: Govee H6003)
  private readonly SERVICE_UUID = '00010203-0405-0607-0809-0a0b0c0d1910'
  private readonly CONTROL_UUID = '00010203-0405-0607-0809-0a0b0c0d2b11'

  async connect() {
    this.server = await connectToDevice(this.SERVICE_UUID)
    const service = await this.server.getPrimaryService(this.SERVICE_UUID)
    this.controlChar = await service.getCharacteristic(this.CONTROL_UUID)
  }

  async setColor(r: number, g: number, b: number) {
    // Device-specific protocol — read from documentation or reverse engineer
    const command = new Uint8Array([
      0x33, 0x05, 0x02,   // Color command header
      r, g, b,             // RGB
      0x00, 0x00, 0x00,   // Padding
      r ^ g ^ b,           // XOR checksum
    ])

    await this.controlChar!.writeValueWithResponse(command)
  }

  async setBrightness(level: number) {
    // level: 0–100
    const command = new Uint8Array([
      0x33, 0x04,
      Math.round(level * 2.55),
      0x00,
    ])
    await this.controlChar!.writeValueWithoutResponse(command)
    // writeValueWithoutResponse — faster, no confirmation
  }

  async readBatteryLevel(): Promise<number> {
    const service = await this.server!.getPrimaryService('battery_service')
    const char = await service.getCharacteristic('battery_level')
    const value = await char.readValue()
    return value.getUint8(0) // 0–100%
  }
}

React Hook

function useBluetooth() {
  const [device, setDevice] = useState<BluetoothRemoteGATTServer | null>(null)
  const [isConnected, setIsConnected] = useState(false)
  const [isSupported] = useState(() => 'bluetooth' in navigator)
  const [error, setError] = useState<string | null>(null)

  const connect = useCallback(async (serviceUUID: string) => {
    try {
      setError(null)
      const server = await connectToDevice(serviceUUID)
      setDevice(server)
      setIsConnected(true)
    } catch (err) {
      if ((err as Error).name === 'NotFoundError') {
        setError('Device not selected')
      } else {
        setError((err as Error).message)
      }
    }
  }, [])

  const disconnect = useCallback(() => {
    device?.disconnect()
    setDevice(null)
    setIsConnected(false)
  }, [device])

  return { device, isConnected, isSupported, error, connect, disconnect }
}

Reconnect After Disconnect

device.addEventListener('gattserverdisconnected', async () => {
  let retries = 0
  while (retries < 3) {
    await new Promise((r) => setTimeout(r, 1000 * (retries + 1)))
    try {
      await device.gatt!.connect()
      await resubscribeCharacteristics()
      console.log('Reconnected')
      return
    } catch {
      retries++
    }
  }
  console.error('Failed to reconnect')
})

What We Do

Study specific BLE device documentation or reverse-engineer protocol (Wireshark + BLE sniffer if no docs). Implement connection, reading characteristics via notifications or polling, writing commands. Add reconnect, error handling, connection status UI.

Timeline: integration with documented BLE device — 3–5 days. Device without documentation (protocol reverse-engineering) — 7–12 days.