const EventEmitter = require("events"); const createDriver = require("../drivers"); class Workout { /** * @param {import("../repositories/sqlite3")} repo * @param {number} id * @param {{ * id: string, * name: string, * driver: string, * connect: string, * maxLevel: number, * }} bike * @param {{ * id: number, * name: string, * cpm: number, * level: number, * warmupMin: number, * warmupCpm: number, * warmupLevel: number, * cooldownCpm: number, * cooldownLevel: number, * }} program * @param {Date} date */ constructor(repo, id, bike, program, date) { this.repo = repo; this.id = id; this.bike = bike; this.program = program; this.date = date; this.driver = null; this.state = "disconnected"; this.offsetSeconds = 0; this.cooldownMin = -1; 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().then((v) => { this.state = this.driver.state; this.events.emit("state", this.state); return v; }); } 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() { 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() { 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() { this.events.emit("destroy"); this.events.removeAllListeners(); if (this.driver != null) { this.driver.destroy(); this.state = this.driver.state; this.events.emit("state", this.state); this.driver = null; } } listMeasurements() { return this.repo.listMeasurements(this.id); } handleworkoutStatus(ws) { if (this.offsetSeconds > 0) { const seconds = (ws.minutes * 60) + ws.seconds + this.offsetSeconds; ws.minutes = Math.floor(seconds / 60); ws.seconds = seconds % 60; for (const key in this.offsets) { if (!this.offsets.hasOwnProperty(key)) { continue; } ws[key] += this.offsets[key]; } } // Steer the level let targetLevel = this.program.level; if (this.cooldownMin > -1 && ws.minutes >= this.cooldownMin) { targetLevel = this.program.cooldownLevel; } else if (this.program.warmupMin > ws.minutes) { targetLevel = this.program.warmupLevel; } if (targetLevel !== ws.level && this.state === "started") { this.driver.setLevel(targetLevel); } this.repo.insertMeasurement({...ws, workoutId: this.id}); this.events.emit("workoutStatus", {...ws}); } setCooldownMin(min) { this.cooldownMin = min; this.events.emit("cooldownMin", min); } /** * @param {import("../repositories/sqlite3")} repo * @param {number} bikeId * @param {number} programId */ static async create(repo, bikeId, programId) { const bike = await repo.findBike(bikeId); if (bike == null) { throw new Error("bike not found"); } const program = await repo.findProgram(programId); if (program == null) { throw new Error("program not found"); } const date = new Date(); const id = await repo.insertWorkout({ bikeId: bike.id, programId: program.id, date: date, cooldownMin: -1, }); return new Workout(repo, id, bike, program, date); } /** * @param {import("../repositories/sqlite3")} repo * @param {number} id */ static async continue(repo, id) { const data = await repo.findWorkout(id); const bike = await repo.findBike(data.bikeId); const program = await repo.findProgram(data.programId); const workout = new Workout(repo, id, bike, program, new Date(data.date)); workout.cooldownMin = data.cooldownMin; const list = await repo.listMeasurements(id); if (list.length > 0) { const last = list[list.length - 1]; workout.offsetSeconds = (last.minutes * 60 + last.seconds); workout.offsets = { distance: last.distance, calories: last.calories, } } return workout; } } module.exports = Workout;