diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index 80f5ee0b7e..90e303600d 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -19,7 +19,7 @@ jobs:
strategy:
matrix:
- node-version: [14.x, 16.x]
+ node-version: [14.x, 18.x]
steps:
- name: Checkout repository
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000000..2640e1df91
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+unsafe-perm=true
+user=0
diff --git a/app/ant/AntManager.js b/app/ant/AntManager.js
deleted file mode 100644
index 8a6bcec4d5..0000000000
--- a/app/ant/AntManager.js
+++ /dev/null
@@ -1,63 +0,0 @@
-'use strict'
-/*
- Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
-
- This manager creates a module to listen to ANT+ devices.
- This currently can be used to get the heart rate from ANT+ heart rate sensors.
-
- Requires an ANT+ USB stick, the following models might work:
- - Garmin USB or USB2 ANT+ or an off-brand clone of it (ID 0x1008)
- - Garmin mini ANT+ (ID 0x1009)
-*/
-import log from 'loglevel'
-import Ant from 'ant-plus'
-import EventEmitter from 'node:events'
-
-function createAntManager () {
- const emitter = new EventEmitter()
- const antStick = new Ant.GarminStick2()
- const antStick3 = new Ant.GarminStick3()
- // it seems that we have to use two separate heart rate sensors to support both old and new
- // ant sticks, since the library requires them to be bound before open is called
- const heartrateSensor = new Ant.HeartRateSensor(antStick)
- const heartrateSensor3 = new Ant.HeartRateSensor(antStick3)
-
- heartrateSensor.on('hbData', (data) => {
- emitter.emit('heartrateMeasurement', { heartrate: data.ComputedHeartRate, batteryLevel: data.BatteryLevel })
- })
-
- heartrateSensor3.on('hbData', (data) => {
- emitter.emit('heartrateMeasurement', { heartrate: data.ComputedHeartRate, batteryLevel: data.BatteryLevel })
- })
-
- antStick.on('startup', () => {
- log.info('classic ANT+ stick found')
- heartrateSensor.attach(0, 0)
- })
-
- antStick3.on('startup', () => {
- log.info('mini ANT+ stick found')
- heartrateSensor3.attach(0, 0)
- })
-
- antStick.on('shutdown', () => {
- log.info('classic ANT+ stick lost')
- })
-
- antStick3.on('shutdown', () => {
- log.info('mini ANT+ stick lost')
- })
-
- if (!antStick.open()) {
- log.debug('classic ANT+ stick NOT found')
- }
-
- if (!antStick3.open()) {
- log.debug('mini ANT+ stick NOT found')
- }
-
- return Object.assign(emitter, {
- })
-}
-
-export { createAntManager }
diff --git a/app/ble/CentralService.js b/app/ble/CentralService.js
deleted file mode 100644
index f8b28a51ea..0000000000
--- a/app/ble/CentralService.js
+++ /dev/null
@@ -1,18 +0,0 @@
-'use strict'
-/*
- Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
-
- Starts the central manager in a forked thread since noble does not like
- to run in the same thread as bleno
-*/
-import { createCentralManager } from './CentralManager.js'
-import process from 'process'
-import config from '../tools/ConfigManager.js'
-import log from 'loglevel'
-
-log.setLevel(config.loglevel.default)
-const centralManager = createCentralManager()
-
-centralManager.on('heartrateMeasurement', (heartrateMeasurement) => {
- process.send(heartrateMeasurement)
-})
diff --git a/app/ble/PeripheralManager.js b/app/ble/PeripheralManager.js
deleted file mode 100644
index 12371f6f63..0000000000
--- a/app/ble/PeripheralManager.js
+++ /dev/null
@@ -1,91 +0,0 @@
-'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 './FtmsPeripheral.js'
-import { createPm5Peripheral } from './Pm5Peripheral.js'
-import log from 'loglevel'
-import EventEmitter from 'node:events'
-
-const modes = ['FTMS', 'FTMSBIKE', 'PM5']
-function createPeripheralManager () {
- const emitter = new EventEmitter()
- let peripheral
- let mode
-
- createPeripheral(config.bluetoothMode)
-
- function getPeripheral () {
- return peripheral
- }
-
- function getPeripheralMode () {
- return mode
- }
-
- function switchPeripheralMode (newMode) {
- // if now mode was passed, select the next one from the list
- if (newMode === undefined) {
- newMode = modes[(modes.indexOf(mode) + 1) % modes.length]
- }
- createPeripheral(newMode)
- }
-
- function notifyMetrics (type, metrics) {
- peripheral.notifyData(type, metrics)
- }
-
- function notifyStatus (status) {
- peripheral.notifyStatus(status)
- }
-
- async function createPeripheral (newMode) {
- if (peripheral) {
- await peripheral.destroy()
- }
-
- if (newMode === 'PM5') {
- log.info('bluetooth profile: Concept2 PM5')
- peripheral = createPm5Peripheral(controlCallback)
- mode = 'PM5'
- } else if (newMode === 'FTMSBIKE') {
- log.info('bluetooth profile: FTMS Indoor Bike')
- peripheral = createFtmsPeripheral(controlCallback, {
- simulateIndoorBike: true
- })
- mode = 'FTMSBIKE'
- } else {
- log.info('bluetooth profile: FTMS Rower')
- peripheral = createFtmsPeripheral(controlCallback, {
- simulateIndoorBike: false
- })
- mode = 'FTMS'
- }
- peripheral.triggerAdvertising()
-
- emitter.emit('control', {
- req: {
- name: 'peripheralMode',
- peripheralMode: mode
- }
- })
- }
-
- function controlCallback (event) {
- emitter.emit('control', event)
- }
-
- return Object.assign(emitter, {
- getPeripheral,
- getPeripheralMode,
- switchPeripheralMode,
- notifyMetrics,
- notifyStatus
- })
-}
-
-export { createPeripheralManager }
diff --git a/app/ble/ftms/DeviceInformationService.js b/app/ble/ftms/DeviceInformationService.js
deleted file mode 100644
index 28f2f64803..0000000000
--- a/app/ble/ftms/DeviceInformationService.js
+++ /dev/null
@@ -1,18 +0,0 @@
-'use strict'
-/*
- Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
-
- todo: Could provide some info on the device here, maybe OS, Node version etc...
-*/
-import bleno from '@abandonware/bleno'
-
-export default class DeviceInformationService extends bleno.PrimaryService {
- constructor (controlPointCallback) {
- super({
- // uuid of "Device Information Service"
- uuid: '180a',
- characteristics: [
- ]
- })
- }
-}
diff --git a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js b/app/ble/ftms/IndoorBikeFeatureCharacteristic.js
deleted file mode 100644
index 4c01098157..0000000000
--- a/app/ble/ftms/IndoorBikeFeatureCharacteristic.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict'
-/*
- Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
-
- This implements the Indoor Bike Feature Characteristic as defined by the specification.
- Used to inform the Central about the features that the Open Rowing Monitor supports.
- Make sure that The Fitness Machine Features and Target Setting Features that are announced here
- are supported in IndoorBikeDataCharacteristic and FitnessMachineControlPointCharacteristic.
-*/
-import bleno from '@abandonware/bleno'
-import log from 'loglevel'
-
-export default class IndoorBikeDataCharacteristic extends bleno.Characteristic {
- constructor (uuid, description, value) {
- super({
- // Fitness Machine Feature
- uuid: '2ACC',
- properties: ['read'],
- value: null
- })
- }
-
- onReadRequest (offset, callback) {
- // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details
- // Fitness Machine Features for the IndoorBikeDataCharacteristic
- // Cadence Supported (1), Total Distance Supported (2), Expended Energy Supported (9),
- // Heart Rate Measurement Supported (10), Elapsed Time Supported (12), Power Measurement Supported (14)
- // 00000110 01010110
- // Target Setting Features for the IndoorBikeDataCharacteristic
- // none
- // 0000000 0000000
- const features = [0x06, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
- log.debug('Features of Indoor Bike requested')
- callback(this.RESULT_SUCCESS, features.slice(offset, features.length))
- }
-}
diff --git a/app/ble/ftms/RowerFeatureCharacteristic.js b/app/ble/ftms/RowerFeatureCharacteristic.js
deleted file mode 100644
index 04e929e85b..0000000000
--- a/app/ble/ftms/RowerFeatureCharacteristic.js
+++ /dev/null
@@ -1,37 +0,0 @@
-'use strict'
-/*
- Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
-
- This implements the Rower Feature Characteristic as defined by the specification.
- Used to inform the Central about the features that the Open Rowing Monitor supports.
- Make sure that The Fitness Machine Features and Target Setting Features that are announced here
- are supported in RowerDataCharacteristic and FitnessMachineControlPointCharacteristic.
-*/
-import bleno from '@abandonware/bleno'
-import log from 'loglevel'
-
-export default class RowerFeatureCharacteristic extends bleno.Characteristic {
- constructor () {
- super({
- // Fitness Machine Feature
- uuid: '2ACC',
- properties: ['read'],
- value: null
- })
- }
-
- onReadRequest (offset, callback) {
- // see https://www.bluetooth.com/specifications/specs/fitness-machine-service-1-0 for details
- // Fitness Machine Features for the RowerDataCharacteristic
- // Total Distance Supported (2), Pace Supported (5), Expended Energy Supported (9),
- // Heart Rate Measurement Supported (10), Elapsed Time Supported (bit 12),
- // Power Measurement Supported (14)
- // 00100100 01010110
- // Target Setting Features for the RowerDataCharacteristic
- // none
- // 0000000 0000000
- const features = [0x24, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
- log.debug('Features of Rower requested')
- callback(this.RESULT_SUCCESS, features.slice(offset, features.length))
- };
-}
diff --git a/app/ble/pm5/DeviceInformationService.js b/app/ble/pm5/DeviceInformationService.js
deleted file mode 100644
index 9741d54269..0000000000
--- a/app/ble/pm5/DeviceInformationService.js
+++ /dev/null
@@ -1,32 +0,0 @@
-'use strict'
-/*
- Open Rowing Monitor, https://github.com/laberning/openrowingmonitor
-
- Provides the required Device Information of the PM5
-*/
-import bleno from '@abandonware/bleno'
-import { constants, getFullUUID } from './Pm5Constants.js'
-import ValueReadCharacteristic from './characteristic/ValueReadCharacteristic.js'
-
-export default class DeviceInformationService extends bleno.PrimaryService {
- constructor () {
- super({
- // InformationenService uuid as defined by the PM5 specification
- uuid: getFullUUID('0010'),
- characteristics: [
- // C2 module number string
- new ValueReadCharacteristic(getFullUUID('0011'), constants.model, 'model'),
- // C2 serial number string
- new ValueReadCharacteristic(getFullUUID('0012'), constants.serial, 'serial'),
- // C2 hardware revision string
- new ValueReadCharacteristic(getFullUUID('0013'), constants.hardwareRevision, 'hardwareRevision'),
- // C2 firmware revision string
- new ValueReadCharacteristic(getFullUUID('0014'), constants.firmwareRevision, 'firmwareRevision'),
- // C2 manufacturer name string
- new ValueReadCharacteristic(getFullUUID('0015'), constants.manufacturer, 'manufacturer'),
- // Erg Machine Type
- new ValueReadCharacteristic(getFullUUID('0016'), constants.ergMachineType, 'ergMachineType')
- ]
- })
- }
-}
diff --git a/app/client/components/AppDialog.js b/app/client/components/AppDialog.js
index eab5d2efae..e37a27901c 100644
--- a/app/client/components/AppDialog.js
+++ b/app/client/components/AppDialog.js
@@ -46,10 +46,15 @@ export class AppDialog extends AppElement {
justify-content: center;
align-items: center;
}
- button:hover {
+ button:hover:not(.disabled) {
filter: brightness(150%);
}
+ button.disabled {
+ filter: brightness(50%);
+ pointer: none
+ }
+
fieldset {
border: 0;
margin: unset;
@@ -67,6 +72,8 @@ export class AppDialog extends AppElement {
padding: 0;
}
`
+ @property({ type: Boolean })
+ isValid = true
@property({ type: Boolean, reflect: true })
dialogOpen
@@ -74,13 +81,13 @@ export class AppDialog extends AppElement {
render () {
return html`
@@ -95,6 +102,13 @@ export class AppDialog extends AppElement {
}
}
+ confirm () {
+ if (this.isValid) {
+ this.close({ target: { returnValue: 'confirm' } })
+ this.dialogOpen = false
+ }
+ }
+
firstUpdated () {
this.dialog.value.showModal()
}
diff --git a/app/client/components/AppElement.js b/app/client/components/AppElement.js
index efe6c39454..59b00cabed 100644
--- a/app/client/components/AppElement.js
+++ b/app/client/components/AppElement.js
@@ -6,22 +6,9 @@
*/
import { LitElement } from 'lit'
-import { property } from 'lit/decorators.js'
-import { APP_STATE } from '../store/appState.js'
export * from 'lit'
export class AppElement extends LitElement {
- // this is how we implement a global state: a global state object is passed via properties
- // to child components
- @property({ type: Object })
- appState = APP_STATE
-
- // ..and state changes are send back to the root component of the app by dispatching
- // a CustomEvent
- updateState () {
- this.sendEvent('appStateChanged', this.appState)
- }
-
// a helper to dispatch events to the parent components
sendEvent (eventType, eventData) {
this.dispatchEvent(
diff --git a/app/client/components/DashboardActions.js b/app/client/components/DashboardActions.js
index 8eb53597a4..60823fc08d 100644
--- a/app/client/components/DashboardActions.js
+++ b/app/client/components/DashboardActions.js
@@ -6,36 +6,52 @@
*/
import { AppElement, html, css } from './AppElement.js'
-import { customElement, state } from 'lit/decorators.js'
-import { icon_undo, icon_expand, icon_compress, icon_poweroff, icon_bluetooth, icon_upload } from '../lib/icons.js'
+import { customElement, property, state } from 'lit/decorators.js'
+import { icon_undo, icon_expand, icon_compress, icon_poweroff, icon_bluetooth, icon_upload, icon_heartbeat, icon_antplus } from '../lib/icons.js'
import './AppDialog.js'
@customElement('dashboard-actions')
export class DashboardActions extends AppElement {
static styles = css`
button {
+ position: relative;
outline:none;
background-color: var(--theme-button-color);
border: 0;
border-radius: var(--theme-border-radius);
color: var(--theme-font-color);
- margin: 0.2em 0;
+ margin: 0.2em 4px;
font-size: 60%;
text-decoration: none;
display: inline-flex;
- width: 3.5em;
- height: 2.5em;
+ width: 3.2em;
+ min-width: 3.2em;
+ height: 2.2em;
justify-content: center;
align-items: center;
}
+
button:hover {
filter: brightness(150%);
}
+ button > div.text {
+ position: absolute;
+ left: 2px;
+ bottom: 2px;
+ font-size: 40%;
+ }
+
#fullscreen-icon {
display: inline-flex;
}
+ .top-button-group {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
#windowed-icon {
display: none;
}
@@ -45,7 +61,7 @@ export class DashboardActions extends AppElement {
}
.peripheral-mode {
- font-size: 80%;
+ font-size: 50%;
}
@media (display-mode: fullscreen) {
@@ -58,25 +74,56 @@ export class DashboardActions extends AppElement {
}
`
- @state({ type: Object })
- dialog
+ @property({ type: Object })
+ config = {}
+
+ @state()
+ _appMode = 'BROWSER'
+
+ @state()
+ _dialog
render () {
return html`
-
- ${this.renderOptionalButtons()}
-
-
${this.peripheralMode()}
- ${this.dialog ? this.dialog : ''}
+
+
+
+
${this.blePeripheralMode()}
+
+ ${this._dialog ? this._dialog : ''}
`
}
+ firstUpdated () {
+ switch (new URLSearchParams(window.location.search).get('mode')) {
+ case 'standalone':
+ this._appMode = 'STANDALONE'
+ break
+ case 'kiosk':
+ this._appMode = 'KIOSK'
+ break
+ default:
+ this._appMode = 'BROWSER'
+ }
+ }
+
renderOptionalButtons () {
const buttons = []
// changing to fullscreen mode only makes sence when the app is openend in a regular
// webbrowser (kiosk and standalone mode are always in fullscreen view) and if the
// browser supports this feature
- if (this.appState?.appMode === 'BROWSER' && document.documentElement.requestFullscreen) {
+ if (this._appMode === 'BROWSER' && document.documentElement.requestFullscreen) {
buttons.push(html`