adds feature to shutdown device, adds some minor UI improvements

This commit is contained in:
Lars Berning 2022-02-12 11:32:31 +00:00
parent 943518b986
commit dec19fdd1f
No known key found for this signature in database
GPG Key ID: 028E73C9E1D8A0B3
7 changed files with 143 additions and 98 deletions

View File

@ -17,10 +17,6 @@ export class AppDialog extends AppElement {
}
static styles = css`
dialog::backdrop {
background: none;
backdrop-filter: contrast(15%) blur(2px);
}
dialog {
border: none;
color: var(--theme-font-color);
@ -30,6 +26,11 @@ export class AppDialog extends AppElement {
padding: 1.6rem;
max-width: 80%;
}
dialog::backdrop {
background: none;
backdrop-filter: contrast(15%) blur(2px);
}
button {
outline:none;
background-color: var(--theme-button-color);
@ -45,6 +46,10 @@ export class AppDialog extends AppElement {
justify-content: center;
align-items: center;
}
button:hover {
filter: brightness(150%);
}
fieldset {
border: 0;
margin: unset;

View File

@ -28,6 +28,9 @@ export class DashboardActions extends AppElement {
justify-content: center;
align-items: center;
}
button:hover {
filter: brightness(150%);
}
#fullscreen-icon {
display: inline-flex;
@ -55,97 +58,108 @@ export class DashboardActions extends AppElement {
}
`
@state({ type: Object })
dialog
@state({ type: Object })
dialog
render () {
return html`
<button @click=${this.reset}>${icon_undo}</button>
${this.renderOptionalButtons()}
<button @click=${this.switchPeripheralMode}>${icon_bluetooth}</button>
<div class="peripheral-mode">${this.peripheralMode()}</div>
${this.dialog ? this.dialog : ''}
render () {
return html`
<button @click=${this.reset}>${icon_undo}</button>
${this.renderOptionalButtons()}
<button @click=${this.switchPeripheralMode}>${icon_bluetooth}</button>
<div class="peripheral-mode">${this.peripheralMode()}</div>
${this.dialog ? this.dialog : ''}
`
}
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) {
buttons.push(html`
<button @click=${this.toggleFullscreen}>
<div id="fullscreen-icon">${icon_expand}</div>
<div id="windowed-icon">${icon_compress}</div>
</button>
`)
}
// add a button to power down the device, if browser is running on the device in kiosk mode
// and the shutdown feature is enabled
// (might also make sence to enable this for all clients but then we would need visual feedback)
if (this.appState?.appMode === 'KIOSK' && this.appState?.config?.shutdownEnabled) {
buttons.push(html`
<button @click=${this.shutdown}>${icon_poweroff}</button>
`)
}
if (this.appState?.config?.stravaUploadEnabled) {
buttons.push(html`
<button @click=${this.uploadTraining}>${icon_upload}</button>
`)
}
return buttons
}
peripheralMode () {
const value = this.appState?.config?.peripheralMode
if (value === 'PM5') {
return 'C2 PM5'
} else if (value === 'FTMSBIKE') {
return 'FTMS Bike'
} else if (value === 'FTMS') {
return 'FTMS Rower'
} else {
return ''
}
}
toggleFullscreen () {
const fullscreenElement = document.getElementsByTagName('web-app')[0]
if (!document.fullscreenElement) {
fullscreenElement.requestFullscreen({ navigationUI: 'hide' })
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
}
}
}
reset () {
this.sendEvent('triggerAction', { command: 'reset' })
}
switchPeripheralMode () {
this.sendEvent('triggerAction', { command: 'switchPeripheralMode' })
}
uploadTraining () {
this.dialog = html`
<app-dialog @close=${dialogClosed}>
<legend>${icon_upload}<br/>Upload to Strava?</legend>
<p>Do you want to finish your workout and upload it to Strava?</p>
</app-dialog>
`
}
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) {
buttons.push(html`
<button @click=${this.toggleFullscreen}>
<div id="fullscreen-icon">${icon_expand}</div>
<div id="windowed-icon">${icon_compress}</div>
</button>
`)
}
// a shutdown button only makes sence when the app is openend as app on a mobile
// device. at some point we might also think of using this to power down the raspi
// when we are running in kiosk mode
if (this.appState.appMode === 'STANDALONE') {
buttons.push(html`
<button @click=${this.close}>${icon_poweroff}</button>
`)
}
if (this.appState.config.stravaUploadEnabled) {
buttons.push(html`
<button @click=${this.uploadTraining}>${icon_upload}</button>
`)
}
return buttons
}
peripheralMode () {
const value = this.appState?.config?.peripheralMode
if (value === 'PM5') {
return 'C2 PM5'
} else if (value === 'FTMSBIKE') {
return 'FTMS Bike'
} else if (value === 'FTMS') {
return 'FTMS Rower'
} else {
return ''
function dialogClosed (event) {
this.dialog = undefined
if (event.detail === 'confirm') {
this.sendEvent('triggerAction', { command: 'uploadTraining' })
}
}
}
toggleFullscreen () {
const fullscreenElement = document.getElementsByTagName('web-app')[0]
if (!document.fullscreenElement) {
fullscreenElement.requestFullscreen({ navigationUI: 'hide' })
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
}
}
}
close () {
window.close()
}
reset () {
this.sendEvent('triggerAction', { command: 'reset' })
}
switchPeripheralMode () {
this.sendEvent('triggerAction', { command: 'switchPeripheralMode' })
}
uploadTraining () {
this.dialog = html`
<app-dialog @close=${dialogClosed}>
<legend>Upload to Strava?</legend>
<p>Do you want to finish your workout and upload it to Strava?</p>
</app-dialog>
`
function dialogClosed (event) {
this.dialog = undefined
if (event.detail === 'confirm') {
this.sendEvent('triggerAction', { command: 'uploadTraining' })
}
shutdown () {
this.dialog = html`
<app-dialog @close=${dialogClosed}>
<legend>${icon_poweroff}<br/>Shutdown Open Rowing Monitor?</legend>
<p>Do you want to shutdown the device?</p>
</app-dialog>
`
function dialogClosed (event) {
this.dialog = undefined
if (event.detail === 'confirm') {
this.sendEvent('triggerAction', { command: 'shutdown' })
}
}
}
}

View File

@ -137,6 +137,10 @@ export function createApp (app) {
if (socket)socket.send(JSON.stringify({ command: 'uploadTraining' }))
break
}
case 'shutdown': {
if (socket)socket.send(JSON.stringify({ command: 'shutdown' }))
break
}
default: {
console.error('no handler defined for action', action)
}

View File

@ -13,7 +13,9 @@ export const APP_STATE = {
config: {
// currently can be FTMS, FTMSBIKE or PM5
peripheralMode: '',
// true if upload to strava is configured
stravaUploadEnabled: false
// true if upload to strava is enabled
stravaUploadEnabled: false,
// true if remote device shutdown is enabled
shutdownEnabled: false
}
}

View File

@ -6,7 +6,8 @@
everything together while figuring out the physics and model of the application.
todo: refactor this as we progress
*/
import { fork } from 'child_process'
import child_process from 'child_process'
import { promisify } from 'util'
import log from 'loglevel'
import config from './tools/ConfigManager.js'
import { createRowingEngine } from './engine/RowingEngine.js'
@ -18,6 +19,7 @@ import { createAntManager } from './ant/AntManager.js'
import { replayRowingSession } from './tools/RowingRecorder.js'
import { createWorkoutRecorder } from './engine/WorkoutRecorder.js'
import { createWorkoutUploader } from './engine/WorkoutUploader.js'
const exec = promisify(child_process.exec)
// set the log levels
log.setLevel(config.loglevel.default)
@ -66,7 +68,7 @@ function resetWorkout () {
peripheralManager.notifyStatus({ name: 'reset' })
}
const gpioTimerService = fork('./app/gpio/GpioTimerService.js')
const gpioTimerService = child_process.fork('./app/gpio/GpioTimerService.js')
gpioTimerService.on('message', handleRotationImpulse)
function handleRotationImpulse (dataPoint) {
@ -110,7 +112,7 @@ rowingStatistics.on('rowingPaused', () => {
})
if (config.heartrateMonitorBLE) {
const bleCentralService = fork('./app/ble/CentralService.js')
const bleCentralService = child_process.fork('./app/ble/CentralService.js')
bleCentralService.on('message', (heartrateMeasurement) => {
rowingStatistics.handleHeartrateMeasurement(heartrateMeasurement)
})
@ -132,7 +134,7 @@ workoutUploader.on('resetWorkout', () => {
})
const webServer = createWebServer()
webServer.on('messageReceived', (message, client) => {
webServer.on('messageReceived', async (message, client) => {
switch (message.command) {
case 'switchPeripheralMode': {
peripheralManager.switchPeripheralMode()
@ -146,6 +148,21 @@ webServer.on('messageReceived', (message, client) => {
workoutUploader.upload(client)
break
}
case 'shutdown': {
if (getConfig().shutdownEnabled) {
console.info('shutting down device...')
try {
const { stdout, stderr } = await exec(config.shutdownCommand)
if (stderr) {
log.error('can not shutdown: ', stderr)
}
log.info(stdout)
} catch (error) {
log.error('can not shutdown: ', error)
}
}
break
}
case 'stravaAuthorizationCode': {
workoutUploader.stravaAuthorizationCode(message.data)
break
@ -164,7 +181,8 @@ webServer.on('clientConnected', (client) => {
function getConfig () {
return {
peripheralMode: peripheralManager.getPeripheralMode(),
stravaUploadEnabled: !!config.stravaClientId && !!config.stravaClientSecret
stravaUploadEnabled: !!config.stravaClientId && !!config.stravaClientSecret,
shutdownEnabled: !!config.shutdownCommand
}
}

View File

@ -101,6 +101,9 @@ export default {
// is the fallback for the default profile settings
rowerSettings: rowerProfiles.DEFAULT,
// command to shutdown the device via the user interface, leave empty to disable this feature
shutdownCommand: 'halt',
// Configures the connection to Strava (to directly upload workouts to Strava)
// Note that these values are not your Strava credentials
// Instead you have to create a Strava API Application as described here:

View File

@ -10,7 +10,6 @@ This is the very minimalistic Backlog for further development of this project.
## Later
* automatically upload recorded rowing sessions to training platforms (i.e. Strava)
* figure out where to set the Service Advertising Data (FTMS.pdf p 15)
* add some attributes to BLE DeviceInformationService
* make Web UI a proper Web Application (tooling and SPA framework)