openrowingmonitor/app/peripherals/PeripheralManager.js

297 lines
7.6 KiB
JavaScript

'use strict'
/*
Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
This manager creates the different Bluetooth Low Energy (BLE) Peripherals and allows
switching between them
*/
import config from '../tools/ConfigManager.js'
import { createFtmsPeripheral } from './ble/FtmsPeripheral.js'
import { createPm5Peripheral } from './ble/Pm5Peripheral.js'
import log from 'loglevel'
import EventEmitter from 'node:events'
import { createCpsPeripheral } from './ble/CpsPeripheral.js'
import { createCscPeripheral } from './ble/CscPeripheral.js'
import AntManager from './ant/AntManager.js'
import { createAntHrmPeripheral } from './ant/HrmPeripheral.js'
import { createBleHrmPeripheral } from './ble/HrmPeripheral.js'
import { createFEPeripheral } from './ant/FEPeripheral.js'
const bleModes = ['FTMS', 'FTMSBIKE', 'PM5', 'CSC', 'CPS', 'OFF']
const antModes = ['FE', 'OFF']
const hrmModes = ['ANT', 'BLE', 'OFF']
function createPeripheralManager () {
const emitter = new EventEmitter()
let _antManager
let blePeripheral
let bleMode
let antPeripheral
let antMode
let hrmPeripheral
let hrmMode
let isPeripheralChangeInProgress = false
setupPeripherals()
async function setupPeripherals () {
await createBlePeripheral(config.bluetoothMode)
await createHrmPeripheral(config.heartRateMode)
await createAntPeripheral(config.antplusMode)
}
function getBlePeripheral () {
return blePeripheral
}
function getBlePeripheralMode () {
return bleMode
}
function getAntPeripheral () {
return antPeripheral
}
function getAntPeripheralMode () {
return antMode
}
function getHrmPeripheral () {
return hrmPeripheral
}
function getHrmPeripheralMode () {
return hrmMode
}
function switchBlePeripheralMode (newMode) {
if (isPeripheralChangeInProgress) return
isPeripheralChangeInProgress = true
// if now mode was passed, select the next one from the list
if (newMode === undefined) {
newMode = bleModes[(bleModes.indexOf(bleMode) + 1) % bleModes.length]
}
createBlePeripheral(newMode)
isPeripheralChangeInProgress = false
}
function notifyMetrics (type, metrics) {
if (bleMode !== 'OFF') { blePeripheral?.notifyData(type, metrics) }
if (antMode !== 'OFF') { antPeripheral?.notifyData(type, metrics) }
}
function notifyStatus (status) {
if (bleMode !== 'OFF') { blePeripheral?.notifyStatus(status) }
if (antMode !== 'OFF') { antPeripheral?.notifyStatus(status) }
}
async function createBlePeripheral (newMode) {
if (blePeripheral) {
await blePeripheral?.destroy()
blePeripheral = undefined
}
switch (newMode) {
case 'PM5':
log.info('bluetooth profile: Concept2 PM5')
blePeripheral = createPm5Peripheral(controlCallback)
bleMode = 'PM5'
break
case 'FTMSBIKE':
log.info('bluetooth profile: FTMS Indoor Bike')
blePeripheral = createFtmsPeripheral(controlCallback, {
simulateIndoorBike: true
})
bleMode = 'FTMSBIKE'
break
case 'CSC':
log.info('bluetooth profile: Cycling Speed and Cadence')
blePeripheral = createCscPeripheral()
bleMode = 'CSC'
break
case 'CPS':
log.info('bluetooth profile: Cycling Power Meter')
blePeripheral = createCpsPeripheral()
bleMode = 'CPS'
break
case 'FTMS':
log.info('bluetooth profile: FTMS Rower')
blePeripheral = createFtmsPeripheral(controlCallback, {
simulateIndoorBike: false
})
bleMode = 'FTMS'
break
default:
log.info('bluetooth profile: Off')
bleMode = 'OFF'
}
if (bleMode.toLocaleLowerCase() !== 'OFF'.toLocaleLowerCase()) { blePeripheral.triggerAdvertising() }
emitter.emit('control', {
req: {
name: 'blePeripheralMode',
peripheralMode: bleMode
}
})
}
function switchAntPeripheralMode (newMode) {
if (isPeripheralChangeInProgress) return
isPeripheralChangeInProgress = true
if (newMode === undefined) {
newMode = antModes[(antModes.indexOf(antMode) + 1) % antModes.length]
}
createAntPeripheral(newMode)
isPeripheralChangeInProgress = false
}
async function createAntPeripheral (newMode) {
if (antPeripheral) {
await antPeripheral?.destroy()
antPeripheral = undefined
try {
if (_antManager && hrmMode !== 'ANT' && newMode === 'OFF') { await _antManager.closeAntStick() }
} catch (error) {
log.error(error)
return
}
}
switch (newMode) {
case 'FE':
log.info('ant plus profile: FE')
if (!_antManager) {
_antManager = new AntManager()
}
try {
antPeripheral = createFEPeripheral(_antManager)
antMode = 'FE'
await antPeripheral.attach()
} catch (error) {
log.error(error)
return
}
break
default:
log.info('ant plus profile: Off')
antMode = 'OFF'
}
emitter.emit('control', {
req: {
name: 'antPeripheralMode',
peripheralMode: antMode
}
})
}
function switchHrmMode (newMode) {
if (isPeripheralChangeInProgress) return
isPeripheralChangeInProgress = true
if (newMode === undefined) {
newMode = hrmModes[(hrmModes.indexOf(hrmMode) + 1) % hrmModes.length]
}
createHrmPeripheral(newMode)
isPeripheralChangeInProgress = false
}
async function createHrmPeripheral (newMode) {
if (hrmPeripheral) {
await hrmPeripheral?.destroy()
hrmPeripheral?.removeAllListeners()
hrmPeripheral = undefined
try {
if (_antManager && newMode !== 'ANT' && antMode === 'OFF') { await _antManager.closeAntStick() }
} catch (error) {
log.error(error)
return
}
}
switch (newMode) {
case 'ANT':
log.info('heart rate profile: ANT')
if (!_antManager) {
_antManager = new AntManager()
}
try {
hrmPeripheral = createAntHrmPeripheral(_antManager)
hrmMode = 'ANT'
await hrmPeripheral.attach()
} catch (error) {
log.error(error)
return
}
break
case 'BLE':
log.info('heart rate profile: BLE')
hrmPeripheral = createBleHrmPeripheral()
hrmMode = 'BLE'
break
default:
log.info('heart rate profile: Off')
hrmMode = 'OFF'
}
if (hrmMode.toLocaleLowerCase() !== 'OFF'.toLocaleLowerCase()) {
hrmPeripheral.on('heartRateMeasurement', (heartRateMeasurement) => {
emitter.emit('heartRateMeasurement', heartRateMeasurement)
})
}
emitter.emit('control', {
req: {
name: 'hrmPeripheralMode',
peripheralMode: hrmMode
}
})
}
function controlCallback (event) {
emitter.emit('control', event)
}
async function shutdownAllPeripherals () {
log.debug('shutting down all peripherals')
try {
await blePeripheral?.destroy()
await antPeripheral?.destroy()
await hrmPeripheral?.destroy()
await _antManager?.closeAntStick()
} catch (error) {
log.error('peripheral shutdown was unsuccessful, restart of Pi may required', error)
}
}
return Object.assign(emitter, {
shutdownAllPeripherals,
getBlePeripheral,
getBlePeripheralMode,
getAntPeripheral,
getAntPeripheralMode,
getHrmPeripheral,
getHrmPeripheralMode,
switchHrmMode,
switchBlePeripheralMode,
switchAntPeripheralMode,
notifyMetrics,
notifyStatus
})
}
export { createPeripheralManager }