HTML та JavaScript мінімальний дослідник Bluetooth/BLE Service за допомогою navigator.bluetooth

Цей самодостатній приклад на HTML та JavaScript демонструє, як використовувати Web Bluetooth API для підключення до Bluetooth-пристрою, виявлення його сервісів та характеристик, а також читання/запису значень.

ble_service_explorer.html
<!doctype html>
<html lang="uk">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Дослідник характеристик BLE-сервісу</title>
  <style>
    body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; padding: 1rem; }
    button { margin: 0.25rem; }
    .characteristic { margin: 0.5rem 0; padding: 0.5rem; border: 1px solid #ddd; border-radius: 6px; }
    .props { font-size: 0.9rem; color: #444; }
    pre { background:#f7f7f7; padding:0.5rem; border-radius:4px; overflow:auto }
  </style>
</head>
<body>
  <h1>Дослідник характеристик BLE-сервісу</h1>
  <p>UUID сервісу: <code id="serviceUuidDisplay">42355f23-e1c4-8481-4282-5add5f9e85aa</code></p>
  <p>
    <button id="connectBtn">Підключитися та вивести характеристики</button>
    <button id="disconnectBtn" disabled>Відключитися</button>
  </p>

  <div id="status">Очікування</div>

  <h2>Характеристики</h2>
  <div id="characteristics"></div>

  <script>
    // TODO Змініть це на ваш UUID сервісу
    const SERVICE_UUID = '42355f23-e1c4-8481-4282-5add5f9e85aa';

    const connectBtn = document.getElementById('connectBtn');
    const disconnectBtn = document.getElementById('disconnectBtn');
    const statusEl = document.getElementById('status');
    const charContainer = document.getElementById('characteristics');
    document.getElementById('serviceUuidDisplay').textContent = SERVICE_UUID;

    let device = null;
    let server = null;
    let service = null;

    function setStatus(text) {
      statusEl.textContent = text;
    }

    function formatProps(props) {
      return Object.keys(props).filter(k => props[k]).join(', ') || 'none';
    }

    async function listCharacteristics() {
      try {
        setStatus('Виявлення характеристик...');
        const characteristics = await service.getCharacteristics();
        charContainer.innerHTML = '';

        if (characteristics.length === 0) {
          charContainer.textContent = 'Для цього сервісу характеристик не знайдено.';
          return;
        }

        for (const ch of characteristics) {
          const el = document.createElement('div');
          el.className = 'characteristic';

          const title = document.createElement('div');
          title.innerHTML = `<strong>UUID:</strong> <code>${ch.uuid}</code>`;
          el.appendChild(title);

          const props = document.createElement('div');
          props.className = 'props';
          props.textContent = 'Властивості: ' + formatProps(ch.properties);
          el.appendChild(props);

          const actions = document.createElement('div');

          // Кнопка читання
          if (ch.properties.read) {
            const readBtn = document.createElement('button');
            readBtn.textContent = 'Читати';
            readBtn.addEventListener('click', async () => {
              try {
                setStatus(`Читання ${ch.uuid}...`);
                const value = await ch.readValue();
                const bytes = new Uint8Array(value.buffer);
                const hex = Array.from(bytes).map(b => b.toString(16).padStart(2,'0')).join(' ');
                showValue(el, hex, bytesToString(bytes));
                setStatus('Читання виконано');
              } catch (err) {
                setStatus('Помилка читання: ' + err);
              }
            });
            actions.appendChild(readBtn);
          }

          // Перемикач сповіщень
          if (ch.properties.notify || ch.properties.indicate) {
            const notifyBtn = document.createElement('button');
            notifyBtn.textContent = 'Увімкнути сповіщення';
            let notifying = false;
            async function handleNotification(e) {
              const v = e.target.value;
              const bytes = new Uint8Array(v.buffer);
              const hex = Array.from(bytes).map(b => b.toString(16).padStart(2,'0')).join(' ');
              showValue(el, hex, bytesToString(bytes), true);
            }
            notifyBtn.addEventListener('click', async () => {
              try {
                if (!notifying) {
                  await ch.startNotifications();
                  ch.addEventListener('characteristicvaluechanged', handleNotification);
                  notifyBtn.textContent = 'Вимкнути сповіщення';
                  notifying = true;
                  setStatus('Сповіщення розпочато для ' + ch.uuid);
                } else {
                  ch.removeEventListener('characteristicvaluechanged', handleNotification);
                  await ch.stopNotifications();
                  notifyBtn.textContent = 'Увімкнути сповіщення';
                  notifying = false;
                  setStatus('Сповіщення зупинено для ' + ch.uuid);
                }
              } catch (err) {
                setStatus('Помилка сповіщень: ' + err);
              }
            });
            actions.appendChild(notifyBtn);
          }

          el.appendChild(actions);
          charContainer.appendChild(el);
        }

        setStatus('Характеристики виведено.');
      } catch (err) {
        setStatus('Помилка виведення характеристик: ' + err);
      }
    }

    function showValue(parentEl, hex, text, append=false) {
      let pre = parentEl.querySelector('pre');
      if (!pre) {
        pre = document.createElement('pre');
        parentEl.appendChild(pre);
      }
      const now = new Date().toISOString();
      const s = `${now}  hex: ${hex}\ntext: ${text}\n`;
      if (append)
        pre.textContent = pre.textContent + '\n' + s;
      else
        pre.textContent = s;
    }

    function bytesToString(bytes) {
      try { return new TextDecoder().decode(bytes); } catch { return '<бінарні дані>'; }
    }

    connectBtn.addEventListener('click', async () => {
      try {
        setStatus('Запит пристрою...');
        device = await navigator.bluetooth.requestDevice({
          acceptAllDevices: true,
          optionalServices: [SERVICE_UUID]
        });

        device.addEventListener('gattserverdisconnected', () => {
          setStatus('Пристрій відключено');
          disconnectBtn.disabled = true;
          connectBtn.disabled = false;
        });

        setStatus('Підключення до GATT-сервера...');
        server = await device.gatt.connect();
        setStatus('Отримання сервісу...');
        service = await server.getPrimaryService(SERVICE_UUID);
        setStatus('Сервіс знайдено: ' + SERVICE_UUID);
        connectBtn.disabled = true;
        disconnectBtn.disabled = false;
        await listCharacteristics();
      } catch (err) {
        setStatus('Помилка підключення: ' + err);
      }
    });

    disconnectBtn.addEventListener('click', async () => {
      if (!device) return;
      try {
        if (device.gatt.connected) device.gatt.disconnect();
        setStatus('Відключено');
        disconnectBtn.disabled = true;
        connectBtn.disabled = false;
      } catch (err) {
        setStatus('Помилка відключення: ' + err);
      }
    });
  </script>
</body>
</html>

Дослідник BLE


Дивіться схожі статті за категоріями: Javascript, Bluetooth