diff --git a/src/api/workout.js b/src/api/workout.js index 6e9c4b4..1b4133b 100644 --- a/src/api/workout.js +++ b/src/api/workout.js @@ -10,6 +10,20 @@ module.exports = function workoutRouter(repo) { /** @type {Workout[]} */ let workouts = []; + function activeData(id) { + const workout = workouts.find(w2 => w2.id === id); + if (workout == null) { + return { + active: false, + }; + } + + return { + active: true, + state: workout.state, + } + } + router.get("/", async(req, res) => { if (req.query.active === "true") { return res.status(200).json(workouts.map(w => ({ @@ -17,7 +31,7 @@ module.exports = function workoutRouter(repo) { bike: w.bike, program: w.program, date: w.date, - active: true, + ...activeData(w.id), }))); } @@ -25,7 +39,7 @@ module.exports = function workoutRouter(repo) { const dbWorkouts = await repo.listWorkouts(); return res.status(200).json(dbWorkouts.map(w => ({ ...w, - active: workouts.find(w2 => w2.id === w.id) != null, + ...activeData(w.id), }))); } catch (err) { return res.status(400).json({code: 400, message: err.message || err}) @@ -35,7 +49,13 @@ module.exports = function workoutRouter(repo) { router.get("/:id", async(req, res) => { let workout = workouts.find(w => w.id == req.params.id); if (workout != null) { - return res.status(200).json({id: workout.id, bike: workout.bike, program: workout.program, date: workout.date, active: true}) + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(w.id), + }); } try { @@ -70,7 +90,13 @@ module.exports = function workoutRouter(repo) { const workout = await Workout.create(repo, bikeId, programId); workouts.push(workout); - return res.status(200).json({id: workout.id, bike: workout.bike, program: workout.program, date: workout.date}); + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(workout.id), + }); } catch (err) { return res.status(400).json({code: 400, message: err.message || err}); } @@ -86,7 +112,13 @@ module.exports = function workoutRouter(repo) { workout = await Workout.continue(repo, req.params.id); workouts.push(workout); - return res.status(200).json({id: workout.id, bike: workout.bike, program: workout.program, date: workout.date}); + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(w.id), + }); } catch (err) { return res.status(400).json({code: 400, message: err.message || err}) } @@ -100,7 +132,13 @@ module.exports = function workoutRouter(repo) { try { await workout.connect(); - return res.status(200).json({id: workout.id, bike: workout.bike, program: workout.program, date: workout.date}); + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(workout.id), + }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } @@ -114,7 +152,13 @@ module.exports = function workoutRouter(repo) { try { await workout.start(); - return res.status(200).json({id: workout.id, bike: workout.bike, program: workout.program, date: workout.date}); + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(workout.id), + }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } @@ -128,7 +172,13 @@ module.exports = function workoutRouter(repo) { try { await workout.pause(); - return res.status(200).json({id: workout.id, bike: workout.bike, program: workout.program, date: workout.date}); + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(workout.id), + }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } @@ -148,7 +198,13 @@ module.exports = function workoutRouter(repo) { workouts = workouts.filter(w => w !== workout); await workout.destroy(); - return res.status(200).json({id: workout.id, bike: workout.bike, program: workout.program, date: workout.date}); + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(workout.id), + }); } catch(err) { return res.status(500).json({code: 500, message: err.message || err}); } @@ -168,15 +224,19 @@ module.exports = function workoutRouter(repo) { await repo.deleteWorkout(workout); - return res.status(200).json(workout); + return res.status(200).json({ + id: workout.id, + bike: workout.bike, + program: workout.program, + date: workout.date, + ...activeData(workout.id), + }); } catch (err) { return res.status(500).json({code: 500, message: err.message || err}) } }); router.ws("/:id/subscribe", (ws, req) => { - console.log("HELLO!") - const workout = workouts.find(w => w.id === req.params.id); if (workout == null) { ws.send(JSON.stringify({error: "workout not found"})); @@ -184,6 +244,8 @@ module.exports = function workoutRouter(repo) { } workout.listMeasurements().then((list) => { + list = list.map(v => {delete v.id; delete v.workoutId; return v}); + for (let i = 0; i < list.length; i += 240) { ws.send(JSON.stringify({workoutStatusBackfill: list.slice(i, i+240)})); } @@ -192,21 +254,28 @@ module.exports = function workoutRouter(repo) { ws.close(); }); - const handler = (workotuStatus) => { - workotuStatus = {...workotuStatus}; - delete workotuStatus.id; + const onWorkoutStatus = (workoutStatus) => { + workoutStatus = {...workoutStatus}; + delete workoutStatus.id; - ws.send(JSON.stringify({workotuStatus})) + ws.send(JSON.stringify({workoutStatus})) + } + workout.events.on("workoutStatus", onWorkoutStatus); + + const onState = (state) => { + ws.send(JSON.stringify({state})) } + workout.events.on("state", onState); - workout.events.on("workoutStatus", handler); ws.onclose = () => { - workout.events.removeListener("workoutStatus", handler); + workout.events.removeListener("workoutStatus", onWorkoutStatus); + workout.events.removeListener("state", onState); ws.removeAllListeners(); }; ws.send(JSON.stringify({ + state: workout.state, workout: { id: workout.id, bike: workout.bike, diff --git a/src/drivers/iconsole.js b/src/drivers/iconsole.js index 9169023..9bfd338 100644 --- a/src/drivers/iconsole.js +++ b/src/drivers/iconsole.js @@ -12,6 +12,7 @@ class IConsoleDriver { this.maxLevel = 24; this.lastLevel = 18; this.workoutStatus = {}; + this.started = false; } async connect() { @@ -36,8 +37,17 @@ class IConsoleDriver { }) } + get state() { + return this.client.state; + } + async start() { - await this.client.start({level: this.lastLevel}); + if (!this.started) { + await this.client.start({level: this.lastLevel}); + this.started = true; + } else { + await this.client.resume(); + } } async pause() { diff --git a/src/drivers/mock.js b/src/drivers/mock.js index cfa0da1..f5c0f99 100644 --- a/src/drivers/mock.js +++ b/src/drivers/mock.js @@ -18,6 +18,7 @@ class MockDriver { watt: 0, level: 1, }; + this.state = "disconnected"; this.interval = null; } @@ -27,6 +28,8 @@ class MockDriver { this.events.emit("maxLevel", 32); }) + this.state = "connected"; + return Promise.resolve(); } @@ -34,6 +37,7 @@ class MockDriver { if (this.started) { return; } + this.state = "started"; this.started = true; this.interval = setInterval(() => { @@ -77,10 +81,15 @@ class MockDriver { pause() { clearInterval(this.interval); this.started = false; + this.state = "connected"; return Promise.resolve(); } + resume() { + return this.start(); + } + stop() { this.pause(); this.workoutStatus = { @@ -95,6 +104,9 @@ class MockDriver { level: 1, }; this.seconds = 0; + this.started = false; + + this.state = "disconnected"; return Promise.resolve(); } diff --git a/src/systems/workout.js b/src/systems/workout.js index 42dc93c..2092fdb 100644 --- a/src/systems/workout.js +++ b/src/systems/workout.js @@ -29,29 +29,65 @@ class Workout { this.date = date; this.driver = null; + this.state = "disconnected"; + this.offsetSeconds = 0; this.offsets = {}; this.events = new EventEmitter(); + + this.events.emit("state", this.state); } async connect() { + if (this.state !== "disconnected") { + return Promise.resolve(); + } + this.driver = createDriver(this.bike.driver, this.bike.connect); this.driver.events.on("workoutStatus", ws => this.handleworkoutStatus(ws)); - return this.driver.connect(); + return this.driver.connect().then((v) => { + this.state = this.driver.state; + this.events.emit("state", this.state); + return v; + }); } start() { - return this.driver.start(); + if (this.state === "started") { + return Promise.resolve(); + } + + return this.driver.start().then((v) => { + this.state = this.driver.state; + this.events.emit("state", this.state); + return v; + }); } pause() { - return this.driver.pause(); + if (this.state !== "started") { + return Promise.resolve(); + } + + return this.driver.pause().then((v) => { + this.state = this.driver.state; + this.events.emit("state", this.state); + return v; + }); } stop() { - return this.driver.stop(); + if (this.state === "started") { + return Promise.resolve(); + } + + return this.driver.stop().then((v) => { + this.state = this.driver.state; + this.events.emit("state", this.state); + return v; + }); } destroy() { @@ -60,6 +96,8 @@ class Workout { if (this.driver != null) { this.driver.destroy(); + this.state = this.driver.state; + this.events.emit("state", this.state); this.driver = null; } }