You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
4.8 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
3 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. const EventEmitter = require("events");
  2. const createDriver = require("../drivers");
  3. class Workout {
  4. /**
  5. * @param {import("../repositories/sqlite3")} repo
  6. * @param {number} id
  7. * @param {{
  8. * id: string,
  9. * name: string,
  10. * driver: string,
  11. * connect: string,
  12. * maxLevel: number,
  13. * }} bike
  14. * @param {{
  15. * id: number,
  16. * name: string,
  17. * cpm: number,
  18. * level: number,
  19. * warmupMin: number,
  20. * warmupCpm: number,
  21. * warmupLevel: number,
  22. * cooldownCpm: number,
  23. * cooldownLevel: number,
  24. * }} program
  25. * @param {Date} date
  26. */
  27. constructor(repo, id, bike, program, date) {
  28. this.repo = repo;
  29. this.id = id;
  30. this.bike = bike;
  31. this.program = program;
  32. this.date = date;
  33. this.driver = null;
  34. this.state = "disconnected";
  35. this.offsetSeconds = 0;
  36. this.cooldownMin = -1;
  37. this.offsets = {};
  38. this.events = new EventEmitter();
  39. this.events.emit("state", this.state);
  40. }
  41. async connect() {
  42. if (this.state !== "disconnected") {
  43. return Promise.resolve();
  44. }
  45. this.driver = createDriver(this.bike.driver, this.bike.connect);
  46. this.driver.events.on("workoutStatus", ws => this.handleworkoutStatus(ws));
  47. return this.driver.connect().then((v) => {
  48. this.state = this.driver.state;
  49. this.events.emit("state", this.state);
  50. return v;
  51. });
  52. }
  53. start() {
  54. if (this.state === "started") {
  55. return Promise.resolve();
  56. }
  57. return this.driver.start().then((v) => {
  58. this.state = this.driver.state;
  59. this.events.emit("state", this.state);
  60. return v;
  61. });
  62. }
  63. pause() {
  64. if (this.state !== "started") {
  65. return Promise.resolve();
  66. }
  67. return this.driver.pause().then((v) => {
  68. this.state = this.driver.state;
  69. this.events.emit("state", this.state);
  70. return v;
  71. });
  72. }
  73. stop() {
  74. if (this.state === "started") {
  75. return Promise.resolve();
  76. }
  77. return this.driver.stop().then((v) => {
  78. this.state = this.driver.state;
  79. this.events.emit("state", this.state);
  80. return v;
  81. });
  82. }
  83. destroy() {
  84. this.events.emit("destroy");
  85. this.events.removeAllListeners();
  86. if (this.driver != null) {
  87. this.driver.destroy();
  88. this.state = this.driver.state;
  89. this.events.emit("state", this.state);
  90. this.driver = null;
  91. }
  92. }
  93. listMeasurements() {
  94. return this.repo.listMeasurements(this.id);
  95. }
  96. handleworkoutStatus(ws) {
  97. if (this.offsetSeconds > 0) {
  98. const seconds = (ws.minutes * 60) + ws.seconds + this.offsetSeconds;
  99. ws.minutes = Math.floor(seconds / 60);
  100. ws.seconds = seconds % 60;
  101. for (const key in this.offsets) {
  102. if (!this.offsets.hasOwnProperty(key)) {
  103. continue;
  104. }
  105. ws[key] += this.offsets[key];
  106. }
  107. }
  108. // Steer the level
  109. let targetLevel = this.program.level;
  110. if (this.cooldownMin > -1 && ws.minutes >= this.cooldownMin) {
  111. targetLevel = this.program.cooldownLevel;
  112. } else if (this.program.warmupMin > ws.minutes) {
  113. targetLevel = this.program.warmupLevel;
  114. }
  115. if (targetLevel !== ws.level && this.state === "started") {
  116. this.driver.setLevel(targetLevel);
  117. }
  118. this.repo.insertMeasurement({...ws, workoutId: this.id});
  119. this.events.emit("workoutStatus", {...ws});
  120. }
  121. setCooldownMin(min) {
  122. this.cooldownMin = min;
  123. this.events.emit("cooldownMin", min);
  124. }
  125. /**
  126. * @param {import("../repositories/sqlite3")} repo
  127. * @param {number} bikeId
  128. * @param {number} programId
  129. */
  130. static async create(repo, bikeId, programId) {
  131. const bike = await repo.findBike(bikeId);
  132. if (bike == null) {
  133. throw new Error("bike not found");
  134. }
  135. const program = await repo.findProgram(programId);
  136. if (program == null) {
  137. throw new Error("program not found");
  138. }
  139. const date = new Date();
  140. const id = await repo.insertWorkout({
  141. bikeId: bike.id,
  142. programId: program.id,
  143. date: date,
  144. cooldownMin: -1,
  145. });
  146. return new Workout(repo, id, bike, program, date);
  147. }
  148. /**
  149. * @param {import("../repositories/sqlite3")} repo
  150. * @param {number} id
  151. */
  152. static async continue(repo, id) {
  153. const data = await repo.findWorkout(id);
  154. const bike = await repo.findBike(data.bikeId);
  155. const program = await repo.findProgram(data.programId);
  156. const workout = new Workout(repo, id, bike, program, new Date(data.date));
  157. workout.cooldownMin = data.cooldownMin;
  158. const list = await repo.listMeasurements(id);
  159. if (list.length > 0) {
  160. const last = list[list.length - 1];
  161. workout.offsetSeconds = (last.minutes * 60 + last.seconds);
  162. workout.offsets = {
  163. distance: last.distance,
  164. calories: last.calories,
  165. }
  166. }
  167. return workout;
  168. }
  169. }
  170. module.exports = Workout;