Создать APK бесплатно

JavaScript bridge API

Фитнес-датчики BLE

Пульс, скорость, каденс, мощность и заряд батареи по стандартным профилям Bluetooth Low Energy.

AppMint.beacons помощник#

AppMint.beacons.startScan(options) / stopScan() / isScanning()

Пример

Находит ближайшие Bluetooth-маяки — iBeacon и Eddystone (UID, URL и TLM) — и сообщает странице о каждом примерно раз в секунду через событие appmint:beacon, с уровнем сигнала и приблизительным расстоянием.

Возвращает: startScan() → Promise с {ok: true} или {ok: false, code} (not_enabled, permission_denied, bluetooth_off, bluetooth_unavailable, scan_failed, blocked). stopScan() → {ok: true}. isScanning() → true/false. Требуется: ничего отмечать не нужно — сборка, код которой вызывает beacons.startScan, объявляет разрешения на сканирование Bluetooth и точную геолокацию (результаты маяков для Android — данные геолокации, поэтому у пользователя запрашиваются оба, а Play относит приложение к использующим геолокацию).

Ищите маяки одного заведения:

window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'ibeacon') {
    showNearest(b.uuid, b.major, b.minor, b.distance);      // metres, a rough estimate
  } else if (b.type === 'eddystone-url') {
    offerLink(b.url);
  } else if (b.type === 'state') {
    console.log('beacon scan ' + b.state);                  // 'scanning' | 'stopped'
  } else if (b.type === 'error') {
    console.warn('beacon scan error', b.error);
  }
});

async function startRanging() {
  if (!(window.AppMint && AppMint.beacons)) return;
  var r = await AppMint.beacons.startScan({ uuids: ['e2c56db5-dffb-48d2-b060-d0f5a71096e0'] });
  if (!r.ok) alert(r.code === 'bluetooth_off' ? 'Turn on Bluetooth' : 'Cannot scan: ' + r.code);
}

Только Eddystone, с телеметрией батареи:

AppMint.beacons.startScan({ ibeacon: false });
window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'eddystone-uid') console.log(b.namespace, b.instance, b.rssi, b.distance);
  if (b.type === 'eddystone-tlm') console.log(b.address, b.batteryMilliVolts + ' mV', b.temperatureC + ' °C');
});

Останавливайте при уходе с экрана:

document.addEventListener('visibilitychange', async function () {
  if (document.visibilityState === 'hidden' && await AppMint.beacons.isScanning()) AppMint.beacons.stopScan();
});

Поля события: у каждого маяка есть type, rssi, address, timestamp; iBeacon добавляет uuid, major, minor, txPower, distance; Eddystone UID добавляет namespace, instance, txPower, distance; URL добавляет url; TLM добавляет batteryMilliVolts, temperatureC, advertCount, uptimeSeconds. uuids фильтрует UUID iBeacon и пространства имён Eddystone (hex в нижнем регистре).

Примечания: Расстояние вычисляется по уровню сигнала и годится только для «близко / далеко» — стены и тела меняют его на метры. Сканирование идёт до stopScan() или закрытия приложения.

beaconIsScanning мост#

window.WebToApk.beaconIsScanning(): Boolean

Идёт ли сканирование маяков.

Пример

Описано вместе с AppMint.beacons — пример там показывает и этот.

Находит ближайшие Bluetooth-маяки — iBeacon и Eddystone (UID, URL и TLM) — и сообщает странице о каждом примерно раз в секунду через событие appmint:beacon, с уровнем сигнала и приблизительным расстоянием.

Возвращает: startScan() → Promise с {ok: true} или {ok: false, code} (not_enabled, permission_denied, bluetooth_off, bluetooth_unavailable, scan_failed, blocked). stopScan() → {ok: true}. isScanning() → true/false. Требуется: ничего отмечать не нужно — сборка, код которой вызывает beacons.startScan, объявляет разрешения на сканирование Bluetooth и точную геолокацию (результаты маяков для Android — данные геолокации, поэтому у пользователя запрашиваются оба, а Play относит приложение к использующим геолокацию).

Ищите маяки одного заведения:

window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'ibeacon') {
    showNearest(b.uuid, b.major, b.minor, b.distance);      // metres, a rough estimate
  } else if (b.type === 'eddystone-url') {
    offerLink(b.url);
  } else if (b.type === 'state') {
    console.log('beacon scan ' + b.state);                  // 'scanning' | 'stopped'
  } else if (b.type === 'error') {
    console.warn('beacon scan error', b.error);
  }
});

async function startRanging() {
  if (!(window.AppMint && AppMint.beacons)) return;
  var r = await AppMint.beacons.startScan({ uuids: ['e2c56db5-dffb-48d2-b060-d0f5a71096e0'] });
  if (!r.ok) alert(r.code === 'bluetooth_off' ? 'Turn on Bluetooth' : 'Cannot scan: ' + r.code);
}

Только Eddystone, с телеметрией батареи:

AppMint.beacons.startScan({ ibeacon: false });
window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'eddystone-uid') console.log(b.namespace, b.instance, b.rssi, b.distance);
  if (b.type === 'eddystone-tlm') console.log(b.address, b.batteryMilliVolts + ' mV', b.temperatureC + ' °C');
});

Останавливайте при уходе с экрана:

document.addEventListener('visibilitychange', async function () {
  if (document.visibilityState === 'hidden' && await AppMint.beacons.isScanning()) AppMint.beacons.stopScan();
});

Поля события: у каждого маяка есть type, rssi, address, timestamp; iBeacon добавляет uuid, major, minor, txPower, distance; Eddystone UID добавляет namespace, instance, txPower, distance; URL добавляет url; TLM добавляет batteryMilliVolts, temperatureC, advertCount, uptimeSeconds. uuids фильтрует UUID iBeacon и пространства имён Eddystone (hex в нижнем регистре).

Примечания: Расстояние вычисляется по уровню сигнала и годится только для «близко / далеко» — стены и тела меняют его на метры. Сканирование идёт до stopScan() или закрытия приложения.

beaconStartScan мост#

window.WebToApk.beaconStartScan(optionsJson: String, callbackId: String)

Запускает поиск маяков с [optionsJson] `{uuids?, ibeacon?, eddystone?}`; завершает [callbackId] значением `{ok}` или `{ok:false, code}`.

Пример

Описано вместе с AppMint.beacons — пример там показывает и этот.

Находит ближайшие Bluetooth-маяки — iBeacon и Eddystone (UID, URL и TLM) — и сообщает странице о каждом примерно раз в секунду через событие appmint:beacon, с уровнем сигнала и приблизительным расстоянием.

Возвращает: startScan() → Promise с {ok: true} или {ok: false, code} (not_enabled, permission_denied, bluetooth_off, bluetooth_unavailable, scan_failed, blocked). stopScan() → {ok: true}. isScanning() → true/false. Требуется: ничего отмечать не нужно — сборка, код которой вызывает beacons.startScan, объявляет разрешения на сканирование Bluetooth и точную геолокацию (результаты маяков для Android — данные геолокации, поэтому у пользователя запрашиваются оба, а Play относит приложение к использующим геолокацию).

Ищите маяки одного заведения:

window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'ibeacon') {
    showNearest(b.uuid, b.major, b.minor, b.distance);      // metres, a rough estimate
  } else if (b.type === 'eddystone-url') {
    offerLink(b.url);
  } else if (b.type === 'state') {
    console.log('beacon scan ' + b.state);                  // 'scanning' | 'stopped'
  } else if (b.type === 'error') {
    console.warn('beacon scan error', b.error);
  }
});

async function startRanging() {
  if (!(window.AppMint && AppMint.beacons)) return;
  var r = await AppMint.beacons.startScan({ uuids: ['e2c56db5-dffb-48d2-b060-d0f5a71096e0'] });
  if (!r.ok) alert(r.code === 'bluetooth_off' ? 'Turn on Bluetooth' : 'Cannot scan: ' + r.code);
}

Только Eddystone, с телеметрией батареи:

AppMint.beacons.startScan({ ibeacon: false });
window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'eddystone-uid') console.log(b.namespace, b.instance, b.rssi, b.distance);
  if (b.type === 'eddystone-tlm') console.log(b.address, b.batteryMilliVolts + ' mV', b.temperatureC + ' °C');
});

Останавливайте при уходе с экрана:

document.addEventListener('visibilitychange', async function () {
  if (document.visibilityState === 'hidden' && await AppMint.beacons.isScanning()) AppMint.beacons.stopScan();
});

Поля события: у каждого маяка есть type, rssi, address, timestamp; iBeacon добавляет uuid, major, minor, txPower, distance; Eddystone UID добавляет namespace, instance, txPower, distance; URL добавляет url; TLM добавляет batteryMilliVolts, temperatureC, advertCount, uptimeSeconds. uuids фильтрует UUID iBeacon и пространства имён Eddystone (hex в нижнем регистре).

Примечания: Расстояние вычисляется по уровню сигнала и годится только для «близко / далеко» — стены и тела меняют его на метры. Сканирование идёт до stopScan() или закрытия приложения.

beaconStopScan мост#

window.WebToApk.beaconStopScan(callbackId: String)

Останавливает поиск маяков; завершает [callbackId] значением `{ok:true}`.

Пример

Описано вместе с AppMint.beacons — пример там показывает и этот.

Находит ближайшие Bluetooth-маяки — iBeacon и Eddystone (UID, URL и TLM) — и сообщает странице о каждом примерно раз в секунду через событие appmint:beacon, с уровнем сигнала и приблизительным расстоянием.

Возвращает: startScan() → Promise с {ok: true} или {ok: false, code} (not_enabled, permission_denied, bluetooth_off, bluetooth_unavailable, scan_failed, blocked). stopScan() → {ok: true}. isScanning() → true/false. Требуется: ничего отмечать не нужно — сборка, код которой вызывает beacons.startScan, объявляет разрешения на сканирование Bluetooth и точную геолокацию (результаты маяков для Android — данные геолокации, поэтому у пользователя запрашиваются оба, а Play относит приложение к использующим геолокацию).

Ищите маяки одного заведения:

window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'ibeacon') {
    showNearest(b.uuid, b.major, b.minor, b.distance);      // metres, a rough estimate
  } else if (b.type === 'eddystone-url') {
    offerLink(b.url);
  } else if (b.type === 'state') {
    console.log('beacon scan ' + b.state);                  // 'scanning' | 'stopped'
  } else if (b.type === 'error') {
    console.warn('beacon scan error', b.error);
  }
});

async function startRanging() {
  if (!(window.AppMint && AppMint.beacons)) return;
  var r = await AppMint.beacons.startScan({ uuids: ['e2c56db5-dffb-48d2-b060-d0f5a71096e0'] });
  if (!r.ok) alert(r.code === 'bluetooth_off' ? 'Turn on Bluetooth' : 'Cannot scan: ' + r.code);
}

Только Eddystone, с телеметрией батареи:

AppMint.beacons.startScan({ ibeacon: false });
window.addEventListener('appmint:beacon', function (e) {
  var b = e.detail;
  if (b.type === 'eddystone-uid') console.log(b.namespace, b.instance, b.rssi, b.distance);
  if (b.type === 'eddystone-tlm') console.log(b.address, b.batteryMilliVolts + ' mV', b.temperatureC + ' °C');
});

Останавливайте при уходе с экрана:

document.addEventListener('visibilitychange', async function () {
  if (document.visibilityState === 'hidden' && await AppMint.beacons.isScanning()) AppMint.beacons.stopScan();
});

Поля события: у каждого маяка есть type, rssi, address, timestamp; iBeacon добавляет uuid, major, minor, txPower, distance; Eddystone UID добавляет namespace, instance, txPower, distance; URL добавляет url; TLM добавляет batteryMilliVolts, temperatureC, advertCount, uptimeSeconds. uuids фильтрует UUID iBeacon и пространства имён Eddystone (hex в нижнем регистре).

Примечания: Расстояние вычисляется по уровню сигнала и годится только для «близко / далеко» — стены и тела меняют его на метры. Сканирование идёт до stopScan() или закрытия приложения.

bleConnect мост#

window.WebToApk.bleConnect(address: String)

Среда выполнения для permBluetooth. Никакого переключателя в мастере нет: сборка включает её, когда собственный код страницы вызывает эти методы (или Web Bluetooth). До этой среды разрешения объявлялись, а в среде выполнения ничто не обращалось к датчику — именно тот шаблон «объявлено, но не работает», который описан в SmsBridge. Результаты приходят как события `appmint:ble` / window.onAppMintBle({kind, ...}).

Описано в своей группе, Фитнес-датчики BLE (пульс / скорость / каденс / мощность / батарея), а не отдельно.

Пример

Подключается к одному фитнес-датчику, найденному bleStartScan(), и начинает передавать его показания.

Возвращает: ничего. Ход и данные приходят как события appmint:ble (и window.onAppMintBle): {kind:'state', state:'connecting', address}, {kind:'state', state:'connected', address}, {kind:'state', state:'ready', services:[...]}, затем {kind:'data', type, data} для каждого показания. Требуется: Bluetooth (включается сборкой из ZIP или HTML, когда ваш код использует мост датчиков) и разрешение «Устройства поблизости», запрашиваемое при первом использовании.

Передайте address из события device при нажатии пользователя:

function connectSensor(address) {
  if (!window.WebToApk || typeof window.WebToApk.bleConnect !== 'function') return;
  window.WebToApk.bleConnect(address);
}

window.onAppMintBle = function (d) {
  if (d.kind === 'state') {
    // 'connecting' | 'connected' | 'ready' | 'disconnected' (also 'scanning' / 'scan_stopped')
    showStatus(d.state);
  } else if (d.kind === 'data') {
    if (d.type === 'heart_rate')    show('bpm', d.data.bpm);
    if (d.type === 'speed_cadence') {                   // either key can be missing in one packet
      if (d.data.speedKmh !== undefined)   show('speed', d.data.speedKmh.toFixed(1) + ' km/h');
      if (d.data.cadenceRpm !== undefined) show('cadence', Math.round(d.data.cadenceRpm) + ' rpm');
    }
    if (d.type === 'power')   show('power', d.data.watts + ' W');
    if (d.type === 'running') show('pace', d.data.speedKmh.toFixed(1) + ' km/h, ' + d.data.cadenceSpm + ' spm');
    if (d.type === 'battery') show('battery', d.data.percent + '%');
  } else if (d.kind === 'error') {
    showStatus('Could not connect: ' + d.error);
  }
};

Запомните подключившийся датчик и подключайтесь к нему в следующий раз без сканирования:

var SAVED_KEY = 'hrStrapAddress';

window.addEventListener('appmint:ble', function (e) {
  var d = e.detail;
  if (d.kind === 'state' && d.state === 'connected') localStorage.setItem(SAVED_KEY, d.address);
  if (d.kind === 'error' && d.error === 'invalid_address') localStorage.removeItem(SAVED_KEY);
});

document.getElementById('start-workout').addEventListener('click', function () {
  if (!window.WebToApk || typeof window.WebToApk.bleConnect !== 'function') return;
  var address = localStorage.getItem(SAVED_KEY);
  if (address) window.WebToApk.bleConnect(address);
  else window.WebToApk.bleStartScan();   // first time: let the user pick a sensor
});

Отслеживайте сессию пульса (среднее, максимум, контакт ремня) по показаниям:

var beats = 0, sum = 0, max = 0;

window.addEventListener('appmint:ble', function (e) {
  var d = e.detail;
  if (d.kind === 'state' && d.state === 'ready' && d.services.length === 0) {
    showStatus('This sensor has no heart rate, speed, power or battery service.');
  }
  if (d.kind !== 'data' || d.type !== 'heart_rate') return;
  if (d.data.contact === false) {          // only sent by straps that report skin contact
    showStatus('Wet the strap: no skin contact');
    return;
  }
  beats++; sum += d.data.bpm; max = Math.max(max, d.data.bpm);
  show('bpm', d.data.bpm);
  show('avg', Math.round(sum / beats));
  show('max', max);
});

Примечания: Один датчик за раз: подключение останавливает любое сканирование и сначала отключает предыдущий датчик (вы получите его disconnected). ready.services перечисляет полные UUID поддерживаемых служб этого датчика. Первый пакет скорости/каденса после подключения ничего не отправляет — для вычисления скорости и каденса нужны два пакета. heart_rate также содержит rrIntervalsMs (интервалы между ударами в мс, когда ремень их отправляет), energyKj и contact, когда ремень их сообщает; power содержит cadenceRpm начиная со второго пакета, когда измеритель отправляет данные шатуна. Ошибки: not_enabled, permission_denied, bluetooth_unavailable, invalid_address. Если датчик выходит из зоны действия, вы получаете {kind:'state', state:'disconnected', address}.

bleDisconnect мост#

window.WebToApk.bleDisconnect()

Среда выполнения для permBluetooth. Никакого переключателя в мастере нет: сборка включает её, когда собственный код страницы вызывает эти методы (или Web Bluetooth). До этой среды разрешения объявлялись, а в среде выполнения ничто не обращалось к датчику — именно тот шаблон «объявлено, но не работает», который описан в SmsBridge. Результаты приходят как события `appmint:ble` / window.onAppMintBle({kind, ...}).

Описано в своей группе, Фитнес-датчики BLE (пульс / скорость / каденс / мощность / батарея), а не отдельно.

Пример

Отключает фитнес-датчик, подключённый через bleConnect().

Возвращает: ничего. Если датчик был подключён, следом приходит событие appmint:ble {kind:'state', state:'disconnected'}. Требуется: Bluetooth (включается сборкой из ZIP или HTML, когда ваш код использует мост датчиков); без него вызов ничего не делает.

document.getElementById('end-workout').addEventListener('click', function () {
  if (window.WebToApk && typeof window.WebToApk.bleDisconnect === 'function') {
    window.WebToApk.bleDisconnect();
  }
});

window.addEventListener('appmint:ble', function (e) {
  if (e.detail.kind === 'state' && e.detail.state === 'disconnected') {
    showStatus('Sensor disconnected');
  }
});

Переподключайтесь сами, когда датчик пропадает, но не когда пользователь закончил тренировку:

var lastAddress = null, userEnded = false, retries = 0;

window.addEventListener('appmint:ble', function (e) {
  var d = e.detail;
  if (d.kind !== 'state') return;
  if (d.state === 'connected') { lastAddress = d.address; userEnded = false; retries = 0; }
  // Only a drop caused by the sensor carries an address.
  if (d.state === 'disconnected' && d.address && !userEnded && lastAddress && retries < 3) {
    retries++;
    showStatus('Sensor lost, reconnecting (' + retries + '/3)...');
    setTimeout(function () { window.WebToApk.bleConnect(lastAddress); }, 3000);
  }
});

function endWorkout() {
  userEnded = true;
  if (window.WebToApk && typeof window.WebToApk.bleDisconnect === 'function') {
    window.WebToApk.bleDisconnect();
  }
}

Отключите и забудьте сохранённый датчик с экрана настроек:

document.getElementById('forget-sensor').addEventListener('click', function () {
  localStorage.removeItem('hrStrapAddress');   // wherever your app saved the address
  if (window.WebToApk && typeof window.WebToApk.bleDisconnect === 'function') {
    window.WebToApk.bleDisconnect();
  }
  showStatus('Sensor forgotten. Tap Scan to pair a new one.');
});

Примечания: У события disconnected, вызванного вами, нет address; у вызванного самим датчиком (вне зоны, выключен) оно есть. Приложение также отключается при закрытии.

bleSetWheelCircumference мост#

window.WebToApk.bleSetWheelCircumference(mm: Int)

Среда выполнения для permBluetooth. Никакого переключателя в мастере нет: сборка включает её, когда собственный код страницы вызывает эти методы (или Web Bluetooth). До этой среды разрешения объявлялись, а в среде выполнения ничто не обращалось к датчику — именно тот шаблон «объявлено, но не работает», который описан в SmsBridge. Результаты приходят как события `appmint:ble` / window.onAppMintBle({kind, ...}).

Описано в своей группе, Фитнес-датчики BLE (пульс / скорость / каденс / мощность / батарея), а не отдельно.

Пример

Задаёт размер колеса велосипеда (длина окружности в миллиметрах), по которому обороты колеса датчика скорости переводятся в км/ч.

Возвращает: ничего. Требуется: ничего для вызова; сами показания скорости требуют подключённого датчика скорости велосипеда (см. bleConnect).

// 2096 mm = 700x23c (the default). 2105 = 700x25c, 2136 = 700x28c, 2070 = 26x2.0 MTB.
function setWheel(mm) {
  if (window.WebToApk && typeof window.WebToApk.bleSetWheelCircumference === 'function') {
    window.WebToApk.bleSetWheelCircumference(mm);
  }
}

document.getElementById('wheel').addEventListener('change', function (e) {
  setWheel(parseInt(e.target.value, 10));
});

Применяйте сохранённый размер колеса гонщика при каждом подключении, так как настройка не переживает перезапуск:

function connectBike(address) {
  if (!window.WebToApk || typeof window.WebToApk.bleConnect !== 'function') return;
  var mm = parseInt(localStorage.getItem('wheelMm'), 10) || 2096;
  window.WebToApk.bleSetWheelCircumference(mm);
  window.WebToApk.bleConnect(address);
}

Откалибруйте по измеренному прокату (один полный оборот колеса, в см) и сами проверьте диапазон, потому что значение вне диапазона игнорируется без ошибки:

document.getElementById('save-rollout').addEventListener('click', function () {
  var mm = Math.round(parseFloat(document.getElementById('rollout-cm').value) * 10);
  if (!(mm >= 500 && mm <= 4000)) {
    showStatus('Enter a roll-out between 50 and 400 cm.');
    return;
  }
  localStorage.setItem('wheelMm', String(mm));
  if (window.WebToApk && typeof window.WebToApk.bleSetWheelCircumference === 'function') {
    window.WebToApk.bleSetWheelCircumference(mm);   // applies to the next speed reading
  }
  showStatus('Wheel set to ' + mm + ' mm');
});

Примечания: Значения вне диапазона 500–4000 мм молча игнорируются (старое значение остаётся). Настройка действует до закрытия приложения, поэтому задавайте её заново после каждого запуска, например прямо перед bleConnect(). Она меняет speedKmh в данных speed_cadence события appmint:ble; каденс не затрагивается.

bleStartScan мост#

window.WebToApk.bleStartScan()

Среда выполнения для permBluetooth. Никакого переключателя в мастере нет: сборка включает её, когда собственный код страницы вызывает эти методы (или Web Bluetooth). До этой среды разрешения объявлялись, а в среде выполнения ничто не обращалось к датчику — именно тот шаблон «объявлено, но не работает», который описан в SmsBridge. Результаты приходят как события `appmint:ble` / window.onAppMintBle({kind, ...}).

Описано в своей группе, Фитнес-датчики BLE (пульс / скорость / каденс / мощность / батарея), а не отдельно.

Пример

Сканирует ближайшие фитнес-датчики Bluetooth (пульс, скорость/каденс велосипеда, мощность, беговые датчики шага) в течение 15 секунд.

Возвращает: ничего. Результаты приходят как события appmint:ble (и window.onAppMintBle(detail)): по одному {kind:'device', address, name, sensors, rssi} на найденный датчик, плюс {kind:'state', state:'scanning'} в начале и {kind:'state', state:'scan_stopped'} в конце. Требуется: Bluetooth. Переключателя для него нет: при сборке из ZIP или HTML-страницы сборка сама включает Bluetooth, если ваш код вызывает bleStartScan / bleConnect или использует navigator.bluetooth. Сборка-сайт (URL) включить его не может. Первое сканирование запрашивает у пользователя разрешение «Устройства поблизости» (геолокация на Android 11 и старше).

Сначала подпишитесь, затем сканируйте по нажатию кнопки:

var found = {};

window.addEventListener('appmint:ble', function (e) {
  var d = e.detail;
  if (d.kind === 'device') {
    found[d.address] = d;
    // d.sensors: any of 'heart_rate', 'speed_cadence', 'power', 'running'
    addRow(d.address, (d.name || 'Unnamed sensor') + ' ' + d.sensors.join(', ') + ' (' + d.rssi + ' dBm)');
  } else if (d.kind === 'state' && d.state === 'scanning') {
    showStatus('Looking for sensors...');
  } else if (d.kind === 'state' && d.state === 'scan_stopped') {
    showStatus(Object.keys(found).length ? 'Pick a sensor' : 'No sensors found. Wake the sensor and try again.');
  } else if (d.kind === 'error') {
    showStatus('Bluetooth error: ' + d.error);
  }
});

document.getElementById('scan').addEventListener('click', function () {
  if (window.WebToApk && typeof window.WebToApk.bleStartScan === 'function') {
    found = {};
    window.WebToApk.bleStartScan();
  } else {
    showStatus('Sensors work in the installed app only.');
  }
});

Автоматически сопрягайте самый сильный нагрудный пульсометр, когда сканирование заканчивается:

var best = null, pairing = false;

window.addEventListener('appmint:ble', function (e) {
  var d = e.detail;
  if (!pairing) return;
  if (d.kind === 'device' && d.sensors.indexOf('heart_rate') !== -1) {
    if (!best || d.rssi > best.rssi) best = d;   // closer strap = higher (less negative) rssi
  } else if (d.kind === 'state' && d.state === 'scan_stopped') {
    pairing = false;
    if (best) window.WebToApk.bleConnect(best.address);
    else showStatus('No heart-rate strap found. Put it on and try again.');
  }
});

document.getElementById('pair-hr').addEventListener('click', function () {
  if (!window.WebToApk || typeof window.WebToApk.bleStartScan !== 'function') return;
  best = null;
  pairing = true;
  window.WebToApk.bleStartScan();
});

Превратите каждую ошибку сканирования в сообщение, по которому пользователь может действовать:

var SCAN_HELP = {
  not_enabled: 'This app was built without Bluetooth.',
  permission_denied: 'Allow Nearby devices for this app in Settings to find sensors.',
  bluetooth_unavailable: 'This phone has no Bluetooth.',
  bluetooth_off: 'Turn Bluetooth on, then tap Scan again.',
  scan_failed: 'The scan could not start. Wait a moment and try again.'
};

window.addEventListener('appmint:ble', function (e) {
  var d = e.detail;
  if (d.kind !== 'error') return;
  var text = SCAN_HELP[d.error] || ('Bluetooth error: ' + d.error);
  if (d.code !== undefined) text += ' (code ' + d.code + ')';
  showStatus(text);
  document.getElementById('scan').disabled = false;
});

Примечания: Коды ошибок в {kind:'error', error}: not_enabled (в сборке нет Bluetooth), permission_denied, bluetooth_unavailable (нет адаптера), bluetooth_off, scan_failed (может содержать code Android). Сканирование сообщает только об устройствах, рекламирующих одну из четырёх фитнес-служб, и каждое устройство только один раз за сканирование. Новое сканирование останавливает старое. Используйте либо слушатель appmint:ble, либо window.onAppMintBle, но не оба — вызываются оба на каждое сообщение.

bleStopScan мост#

window.WebToApk.bleStopScan()

Среда выполнения для permBluetooth. Никакого переключателя в мастере нет: сборка включает её, когда собственный код страницы вызывает эти методы (или Web Bluetooth). До этой среды разрешения объявлялись, а в среде выполнения ничто не обращалось к датчику — именно тот шаблон «объявлено, но не работает», который описан в SmsBridge. Результаты приходят как события `appmint:ble` / window.onAppMintBle({kind, ...}).

Описано в своей группе, Фитнес-датчики BLE (пульс / скорость / каденс / мощность / батарея), а не отдельно.

Пример

Останавливает сканирование датчиков, начатое bleStartScan(), до истечения его 15 секунд.

Возвращает: ничего. Если сканирование шло, следом приходит событие appmint:ble {kind:'state', state:'scan_stopped'}. Требуется: Bluetooth (включается сборкой из ZIP или HTML, когда ваш код использует мост датчиков); без него вызов ничего не делает.

window.addEventListener('appmint:ble', function (e) {
  if (e.detail.kind === 'state' && e.detail.state === 'scan_stopped') {
    document.getElementById('scan').disabled = false;
  }
});

document.getElementById('stop-scan').addEventListener('click', function () {
  if (window.WebToApk && typeof window.WebToApk.bleStopScan === 'function') {
    window.WebToApk.bleStopScan();
  }
});

Останавливайтесь раньше, как только появляется нужный вам тип датчика:

window.addEventListener('appmint:ble', function (e) {
  var d = e.detail;
  if (d.kind === 'device' && d.sensors.indexOf('power') !== -1) {
    if (window.WebToApk && typeof window.WebToApk.bleStopScan === 'function') {
      window.WebToApk.bleStopScan();   // no need to wait the full 15 seconds
    }
    showPowerMeter(d.address, d.name || 'Power meter');
  }
});

Остановите сканирование, когда пользователь закрывает окно сопряжения или покидает приложение:

function stopLooking() {
  if (window.WebToApk && typeof window.WebToApk.bleStopScan === 'function') {
    window.WebToApk.bleStopScan();   // safe even if the scan already ended
  }
}

document.getElementById('close-pairing').addEventListener('click', function () {
  stopLooking();
  document.getElementById('pairing-sheet').hidden = true;
});

document.addEventListener('visibilitychange', function () {
  if (document.hidden) stopLooking();
});

Примечания: Вам это редко нужно: сканирование останавливается само через 15 секунд, и bleConnect() тоже его останавливает. Вызов, когда сканирование не идёт, безопасен и не отправляет событий.