adds feature to shutdown device, adds some minor UI improvements
This commit is contained in:
parent
943518b986
commit
dec19fdd1f
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue