diff --git a/src/drivers/iconsole.js b/src/drivers/iconsole.js new file mode 100644 index 0000000..aaab96c --- /dev/null +++ b/src/drivers/iconsole.js @@ -0,0 +1,67 @@ +const Client = require("iconsole-bike-client"); +const _ = require("underscore"); + +class IConsoleDriver { + constructor(connect) { + this.connectString = connect; + this.client = null; + this.maxLevel = 24; + this.lastLevel = 18; + + this.workoutState = {}; + } + + async connect() { + this.client = await Client.scan(this.connectString, 15000) + + this.client.events.on("maxLevel", ({maxLevel}) => { + this.maxLevel = maxLevel; + + this.events.emit("maxLevel", maxLevel); + }); + + this.client.events.on("workoutState", ({workoutState}) => { + this.workoutState = workoutState; + + if (!_.isEqual(this.workoutState, workoutState)) { + this.workoutState = {...workoutState}; + this.events.emit("workoutState", {...workoutState}); + } + }); + } + + async start() { + await this.client.start({level: this.lastLevel}); + } + + async pause() { + await this.client.pause(); + } + + async resume() { + await this.client.resume(); + } + + async stop() { + await this.client.pause(); + } + + async setLevel(n) { + if (n < 0 || n > this.maxLevel) { + return Promise.reject(new Error(`Level is out of range (0 - ${this.maxLevel})`)) + } + + this.lastLevel = n; + + return await this.client.setLevel(n); + } + + destroy() { + this.stop(); + this.client.disconnect(); + this.events.emit("destroy"); + this.events.removeAllListeners(); + } +} + +module.exports = IConsoleDriver; \ No newline at end of file diff --git a/src/drivers/index.js b/src/drivers/index.js index 9802cb6..37b7e8f 100644 --- a/src/drivers/index.js +++ b/src/drivers/index.js @@ -1,9 +1,12 @@ const MockDriver = require("./mock"); +const IConsoleDriver = require("./iconsole"); -module.exports = function createDriver(name) { +module.exports = function createDriver(name, connect) { switch (name) { case "mock": - return new MockDriver(); + return new MockDriver(connect); + case "iconsole": + return new IConsoleDriver(connect); default: throw new Error(`Driver ${name} doesn't exist!`) } diff --git a/src/drivers/mock.js b/src/drivers/mock.js index 9ce2f81..245df10 100644 --- a/src/drivers/mock.js +++ b/src/drivers/mock.js @@ -23,6 +23,10 @@ class MockDriver { } connect() { + setTimeout(() => { + this.events.emit("maxLevel", 32); + }) + return Promise.resolve(); } @@ -66,6 +70,8 @@ class MockDriver { } this.level = n; + + return Promise.resolve(); } pause() { diff --git a/src/repositories/sqlite3.js b/src/repositories/sqlite3.js index 2f0b3d6..3a7f258 100644 --- a/src/repositories/sqlite3.js +++ b/src/repositories/sqlite3.js @@ -1,5 +1,16 @@ const sqlite3 = require("sqlite3").verbose(); +function generateId() { + const buffer = Buffer.alloc(18); + buffer.writeUInt32BE(Date.now() / (2 ** 32), 0); + buffer.writeUInt32BE(Date.now() % (2 ** 32), 4); + for (let i = 8; i < 18; ++i) { + buffer[i] = Math.floor(Math.random() * 255); + } + + return buffer.slice(2).toString("hex"); +} + class SQLite3Repository { constructor(path) { this.db = new sqlite3.Database(path); @@ -39,15 +50,21 @@ class SQLite3Repository { } insert(obj, table, params) { + if (!params.includes("id")) { + params.push("id"); + obj = {...obj, id: table.charAt(0).toUpperCase() + generateId(0, -1)}; + } + let id = obj.id; + return new Promise((resolve, reject) => { const query = `INSERT INTO ${table} (${params.join(", ")}) VALUES (${params.map(p => ':'+p).join(", ")});`; const values = params.map(p => obj[p]); - this.db.run(query, values, function(err) { + this.db.run(query, values, (err) => { if (err != null) { return reject(err); } - resolve(this.lastID); + resolve(id); }); }); } @@ -181,7 +198,7 @@ class SQLite3Repository { const SQL_TABLE_BIKE = ` CREATE TABLE IF NOT EXISTS bike ( - id INTEGER PRIMARY KEY, + id CHAR(32) PRIMARY KEY, name VARCHAR(255) NOT NULL, driver VARCHAR(255) NOT NULL, connect VARCHAR(255) NOT NULL, @@ -191,7 +208,7 @@ const SQL_TABLE_BIKE = ` const SQL_TABLE_PROGRAM = ` CREATE TABLE IF NOT EXISTS program ( - id INTEGER PRIMARY KEY, + id CHAR(32) PRIMARY KEY, name VARCHAR(255) NOT NULL, cpm INT NOT NULL, warmupMin INT NOT NULL, @@ -201,7 +218,7 @@ const SQL_TABLE_PROGRAM = ` const SQL_TABLE_WORKOUT = ` CREATE TABLE IF NOT EXISTS workout ( - id INTEGER PRIMARY KEY, + id CHAR(32) PRIMARY KEY, bikeId INT NOT NULL, programId INT NOT NULL, date DATETIME NOT NULL @@ -210,7 +227,7 @@ const SQL_TABLE_WORKOUT = ` const SQL_TABLE_MEASUREMENT = ` CREATE TABLE IF NOT EXISTS measurement ( - id INTEGER PRIMARY KEY, + id CHAR(32) PRIMARY KEY, workoutId INT NOT NULL, minutes INT, seconds INT, diff --git a/src/server.js b/src/server.js index 98c18a1..1c4ee9b 100644 --- a/src/server.js +++ b/src/server.js @@ -6,6 +6,8 @@ const repo = new SQLite3Repository("stuff.db"); repo.setup().catch(err => { console.error("Failed to setup db:", err); process.exit(1); +}).then(() => { + console.log("Datbase setup complete.") }); const app = express(); @@ -16,5 +18,9 @@ app.use("/api/bike/", require("./api/bike")(repo)); app.use("/api/program/", require("./api/program")(repo)); app.use("/api/workout/", require("./api/workout")(repo)); -app.listen(8780); +const port = process.env.PORT || 8780; +const host = process.env.HOST || "0.0.0.0"; +app.listen(port, host, () => { + console.log(`Listening on ${host}:${port}...`) +}); diff --git a/src/systems/workout.js b/src/systems/workout.js index e88fd21..c1b109f 100644 --- a/src/systems/workout.js +++ b/src/systems/workout.js @@ -36,7 +36,7 @@ class Workout { } async connect() { - this.driver = createDriver(this.bike.driver); + this.driver = createDriver(this.bike.driver, this.bike.connect); this.driver.events.on("workoutState", ws => this.handleWorkoutState(ws)); return this.driver.connect();