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() тоже его останавливает. Вызов, когда сканирование не идёт, безопасен и не отправляет событий.