JavaScript bridge API
BLE fitness sensors
Heart rate, speed, cadence, power and battery from standard Bluetooth Low Energy profiles.
bleConnect bridge#
window.WebToApk.bleConnect(address: String)
Runtime for permBluetooth. No wizard switch sets it: the build arms it when the page's own code calls these methods (or Web Bluetooth). Before this runtime the permissions were declared while nothing in the runtime touched a sensor, the exact declared-but-dead pattern SmsBridge documents. Results stream as `appmint:ble` events / window.onAppMintBle({kind, ...}).
Described by its group, BLE fitness sensors (heart rate / speed / cadence / power / battery), rather than on its own.
Example
Connects to one fitness sensor found by bleStartScan() and starts streaming its readings.
Returns: nothing. Progress and data arrive as appmint:ble events (and window.onAppMintBle): {kind:'state', state:'connecting', address}, {kind:'state', state:'connected', address}, {kind:'state', state:'ready', services:[...]}, then {kind:'data', type, data} for every reading. Needs: Bluetooth (turned on by a ZIP or HTML build when your code uses the sensor bridge) and the Nearby devices permission, asked on first use.
Pass the address from a device event, from the user's tap:
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);
}
};Notes: One sensor at a time: connecting stops any scan and drops the previous sensor first (you get its disconnected). ready.services lists the full UUIDs of the supported services this sensor has. The first speed/cadence packet after connecting sends nothing - speed and cadence need two packets to compute. heart_rate also carries rrIntervalsMs (beat-to-beat intervals in ms, when the strap sends them), energyKj and contact when the strap reports them; power carries cadenceRpm from its second packet on when the meter sends crank data. Errors: not_enabled, permission_denied, bluetooth_unavailable, invalid_address. If the sensor walks out of range you get {kind:'state', state:'disconnected', address}.
bleDisconnect bridge#
window.WebToApk.bleDisconnect()
Runtime for permBluetooth. No wizard switch sets it: the build arms it when the page's own code calls these methods (or Web Bluetooth). Before this runtime the permissions were declared while nothing in the runtime touched a sensor, the exact declared-but-dead pattern SmsBridge documents. Results stream as `appmint:ble` events / window.onAppMintBle({kind, ...}).
Described by its group, BLE fitness sensors (heart rate / speed / cadence / power / battery), rather than on its own.
Example
Disconnects the fitness sensor connected with bleConnect().
Returns: nothing. If a sensor was connected, an appmint:ble event {kind:'state', state:'disconnected'} follows. Needs: Bluetooth (turned on by a ZIP or HTML build when your code uses the sensor bridge); without it the call does nothing.
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');
}
});Notes: The disconnected event you cause yourself has no address; one caused by the sensor (out of range, switched off) has it. The app also disconnects when it closes.
bleSetWheelCircumference bridge#
window.WebToApk.bleSetWheelCircumference(mm: Int)
Runtime for permBluetooth. No wizard switch sets it: the build arms it when the page's own code calls these methods (or Web Bluetooth). Before this runtime the permissions were declared while nothing in the runtime touched a sensor, the exact declared-but-dead pattern SmsBridge documents. Results stream as `appmint:ble` events / window.onAppMintBle({kind, ...}).
Described by its group, BLE fitness sensors (heart rate / speed / cadence / power / battery), rather than on its own.
Example
Sets the bike wheel size (circumference in millimetres) used to turn a speed sensor's wheel turns into km/h.
Returns: nothing. Needs: nothing to call it; the speed readings themselves need a connected cycling speed sensor (see 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));
});Notes: Values outside 500-4000 mm are ignored silently (the old value stays). The setting lasts until the app closes, so set it again after each launch, for example right before bleConnect(). It changes speedKmh in the speed_cadence data of appmint:ble; cadence is not affected.
bleStartScan bridge#
window.WebToApk.bleStartScan()
Runtime for permBluetooth. No wizard switch sets it: the build arms it when the page's own code calls these methods (or Web Bluetooth). Before this runtime the permissions were declared while nothing in the runtime touched a sensor, the exact declared-but-dead pattern SmsBridge documents. Results stream as `appmint:ble` events / window.onAppMintBle({kind, ...}).
Described by its group, BLE fitness sensors (heart rate / speed / cadence / power / battery), rather than on its own.
Example
Scans for nearby Bluetooth fitness sensors (heart rate, cycling speed/cadence, power, running footpods) for 15 seconds.
Returns: nothing. Results arrive as appmint:ble events (and window.onAppMintBle(detail)): one {kind:'device', address, name, sensors, rssi} per sensor found, plus {kind:'state', state:'scanning'} at the start and {kind:'state', state:'scan_stopped'} at the end. Needs: Bluetooth. There is no switch for it: when you build from a ZIP or an HTML page, the build turns Bluetooth on by itself if your code calls bleStartScan / bleConnect or uses navigator.bluetooth. A website (URL) build cannot turn it on. The first scan asks the user for the Nearby devices permission (Location on Android 11 and older).
Listen first, then scan from a button tap:
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.');
}
});Notes: Error codes in {kind:'error', error}: not_enabled (the build has no Bluetooth), permission_denied, bluetooth_unavailable (no adapter), bluetooth_off, scan_failed (may carry the Android code). The scan only reports devices that advertise one of the four fitness services, and each device only once per scan. Starting a new scan stops the old one. Use either the appmint:ble listener or window.onAppMintBle, not both - both are called for every message.
bleStopScan bridge#
window.WebToApk.bleStopScan()
Runtime for permBluetooth. No wizard switch sets it: the build arms it when the page's own code calls these methods (or Web Bluetooth). Before this runtime the permissions were declared while nothing in the runtime touched a sensor, the exact declared-but-dead pattern SmsBridge documents. Results stream as `appmint:ble` events / window.onAppMintBle({kind, ...}).
Described by its group, BLE fitness sensors (heart rate / speed / cadence / power / battery), rather than on its own.
Example
Stops a sensor scan started with bleStartScan() before its 15 seconds are up.
Returns: nothing. If a scan was running, an appmint:ble event {kind:'state', state:'scan_stopped'} follows. Needs: Bluetooth (turned on by a ZIP or HTML build when your code uses the sensor bridge); without it the call does nothing.
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();
}
});Notes: You rarely need this: the scan stops by itself after 15 seconds, and bleConnect() stops it too. Calling it when no scan is running is safe and sends no event.