diff --git a/app.js b/app.js index d756fc4..9725d9d 100644 --- a/app.js +++ b/app.js @@ -21,13 +21,184 @@ require('./lib/config/express')(app); // Routing require('./lib/routes')(app); -// socket.io initialization +// Create our HTTP server var server = http.createServer(app).listen(config.port, "0.0.0.0"); + +// Initialize our services +var MinerService = new (require('./lib/services/MinersService.js'))(); +var CoinService = new (require('./lib/services/CoinsService.js'))(); +var PoolService = new (require('./lib/services/PoolsService.js'))(); + +// socket.io initialization var io = require('socket.io').listen(server); -io.set('log level', 1); // reduce logging + +// reduce logging +io.set('log level', 1); // Listen to socket.io connection -io.sockets.on('connection', socket); +//io.sockets.on('connection', socket); + +io.sockets.on('connection', function (socket) { + socket.emit('miners:init', MinerService.miners); + socket.emit('coins:init', CoinService.coins); + socket.emit('pools:init', PoolService.pools); + + /* + * Socket.IO Event Listeners + */ + socket.on('save:miners', function (miners) { + MinerService.save(miners); + }); + + socket.on('save:coins', function (coins) { + CoinService.save(coins); + }); + + socket.on('save:pools', function (pools) { + PoolService.save(pools); + }); + + socket.on('gpu:enable', function (data) { + MinerService.enableGpu(data.miner, data.device); + }); + + socket.on('gpu:disable', function (data) { + MinerService.disableGpu(data.miner, data.device); + }); + + socket.on('update:intensity', function (data) { + MinerService.updateIntensity(data.miner, data.device, data.value); + }); + + socket.on('update:gpuengine', function (data) { + MinerService.updateGpuEngine(data.miner, data.device, data.value); + }); + + socket.on('update:gpumemory', function (data) { + MinerService.updateGpuMemory (data.miner, data.device, data.value); + }); + + socket.on('update:gpuvoltage', function (data) { + MinerService.updateGpuVoltage (data.miner, data.device, data.value); + }); + + socket.on('zero:miner', function (miner) { + MinerService.zeroMiner(miner); + }); + + socket.on('zero:allminers', function () { + MinerService.miners.forEach(function (miner) { + MinerService.zeroMiner(miner); + }); + }); + + socket.on('change:pool', function (data) { + MinerService.changePool(data.miner, data.pool); + }); + + /* + * Miner Service Event Listeners + */ + MinerService.on('update', function (miner) { + socket.emit('miner:config', miner); + }); + + MinerService.on('saved', function (miners) { + socket.emit('saved:miners', miners); + }); + + MinerService.on('loaded', function (miners) { + socket.emit('miners:init', miners); + }); + + MinerService.on('error:miner', function (data) { + socket.emit('error:miner', { miner: data.miner, error: data.error }); + }); + + MinerService.on('fileError', function (msg, data) { + socket.emit('error:file', { msg: msg, data: data }); + }); + + MinerService.on('success:gpuenable', function (data) { + socket.emit('success:gpuenable', data); + }); + + MinerService.on('success:gpudisable', function (data) { + socket.emit('success:gpudisable', data); + }); + + MinerService.on('error:gpuenable', function (data) { + socket.emit('error:gpuenable', data); + }); + + MinerService.on('error:gpudisable', function (data) { + socket.emit('error:gpudisable', data); + }); + + MinerService.on('success:intensity', function (data) { + socket.emit('success:intensity', data); + }); + + MinerService.on('error:intensity', function (data) { + socket.emit('error:intensity', data); + }); + + MinerService.on('success:gpuengine', function (data) { + socket.emit('success:gpuengine', data); + }); + + MinerService.on('error:gpuengine', function (data) { + socket.emit('error:gpuengine', data); + }); + + MinerService.on('success:gpumemory', function (data) { + socket.emit('success:gpumemory', data); + }); + + MinerService.on('error:gpumemory', function (data) { + socket.emit('error:gpumemory', data); + }); + + MinerService.on('success:gpuvoltage', function (data) { + socket.emit('success:gpuvoltage', data); + }); + + MinerService.on('error:gpuvoltage', function (data) { + socket.emit('error:gpuvoltage', data); + }); + + MinerService.on('success:zerominer', function (data) { + socket.emit('success:zerominer', { miner:data.miner, status:data.status }); + }); + + MinerService.on('error:zerominer', function (data) { + socket.emit('error:zerominer', { miner:data.miner, status:data.status }); + }); + + MinerService.on('success:changepool', function (data) { + socket.emit('success:changepool', { miner: data.miner, pool: data.pool, status: data.status }); + }); + + MinerService.on('error:changepool', function (data) { + socket.emit('error:changepool', { miner: data.miner, pool: data.pool, status: data.status }); + }); + + /** + * Coins Service Event Listeners + */ + + CoinService.on('saved', function (coins) { + socket.emit('saved:coins', coins); + }); + + /** + * Pool Service Event Listeners + */ + + PoolService.on('saved', function (pools) { + socket.emit('saved:coins', pools); + }); +}); // Expose app var exports = module.exports = server; \ No newline at end of file diff --git a/lib/controllers/api.js b/lib/controllers/api.js index b54cd0e..f04af9d 100644 --- a/lib/controllers/api.js +++ b/lib/controllers/api.js @@ -1,28 +1,23 @@ -var minerApi = require('../modules/bfgminer'); - 'use strict'; -/** - * Get awesome things - */ -exports.pingMiner = function (req, res) { +var MinerService = new (require('../services/MinersService.js'))(); + +exports.ping = function (req, res) { var hostName = req.params.hostname; var port = req.params.port; - minerApi.ping({ host: hostName, port: port }, function (err, data) { - if (err) { - res.json({ - host: hostName, - port: port, - online: false - }); - } else { - res.json({ - host: hostName, - port: port, - online: true, - status: data - }); - } + MinerService.ping({ host: hostName, port: port}).then(function (data) { + res.json({ + host: hostName, + port: port, + online: true, + status: data + }); + }, function (err) { + res.json({ + host: hostName, + port: port, + online: false + }); }); }; \ No newline at end of file diff --git a/lib/helpers.js b/lib/helpers.js index 514517c..97398b2 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,3 +1,5 @@ +var _ = require('lodash'); + exports.parseDevDetails = function (miner, response) { var data; diff --git a/lib/routes.js b/lib/routes.js index 9e896c4..b075831 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -9,7 +9,7 @@ var api = require('./controllers/api'), module.exports = function(app) { // Server API Routes - app.get('/api/miner/ping/:hostname/:port', api.pingMiner) + app.get('/api/miner/ping/:hostname/:port', api.ping) // All other routes to use Angular routing in app/scripts/app.js app.get('/partials/*', index.partials); diff --git a/lib/services/CoinsService.js b/lib/services/CoinsService.js new file mode 100644 index 0000000..c9b585c --- /dev/null +++ b/lib/services/CoinsService.js @@ -0,0 +1,61 @@ +/** + * Dependencies + */ + +var EventEmitter = require('events').EventEmitter, + util = require('util'), + fs = require('fs'), + path = require('path'), + _ = require('lodash'), + helpers = require('../helpers'); + +function CoinsService() { + EventEmitter.call(this); + var self = this; + + if (!self.coins || self.coins.length == 0) { + self.load(); + } +}; + +util.inherits(CoinsService, EventEmitter); + +CoinsService.prototype.load = function () { + var self = this; + + var file = path.normalize(__dirname + "/../config/coins.json"); + + try { + var data = fs.readFileSync(file, 'utf8'); + + this.coins = JSON.parse(data); + + } catch (err) { + setTimeout(function() { + self.emit('fileError', { msg: 'Error reading coin configuration', data: err }); + }, 0); + } +}; + +CoinsService.prototype.save = function (coins) { + var self = this; + + var file = path.normalize(__dirname + "/../config/coins.json"); + + fs.writeFile(file, JSON.stringify(coins, null, 4), function (err) { + if (err) { + setTimeout(function() { + self.emit('fileError', { msg: 'Error saving coin configuration', data: err }); + }, 0); + return; + } + + setTimeout(function() { + self.emit('saved', coins); + self.coins = coins; + }, 0); + }); +}; + +module.exports = CoinsService; + diff --git a/lib/services/MinersService.js b/lib/services/MinersService.js new file mode 100644 index 0000000..365a11e --- /dev/null +++ b/lib/services/MinersService.js @@ -0,0 +1,280 @@ +/** + * Dependencies + */ + +var EventEmitter = require('events').EventEmitter, + util = require('util'), + xgminer = require('xgminer'), + fs = require('fs'), + path = require('path'), + _ = require('lodash'), + helpers = require('../helpers'); + +function MinersService() { + EventEmitter.call(this); + var self = this; + + if (!self.miners || self.miners.length == 0) { + self.load(); + } + + if (self.miners && self.miners.length > 0) { + self.miners.forEach(function (miner) { + self.poll(miner) + }); + + // TODO: Make interval time read from config + self.pollInterval = setInterval(function () { + self.miners.forEach(function (miner) { + self.poll(miner) + }); + }, 3000); + } +}; + +util.inherits(MinersService, EventEmitter); + +/** + * Methods + */ + +MinersService.prototype.load = function () { + var self = this; + + var file = path.normalize(__dirname + "/../config/miners.json"); + + try { + var data = fs.readFileSync(file, 'utf8'); + + this.miners = JSON.parse(data); + + } catch (err) { + setTimeout(function() { + self.emit('fileError', { msg: 'Error reading miner configuration', data: err }); + }, 0); + } +}; + +MinersService.prototype.save = function (miners) { + var self = this; + + var file = path.normalize(__dirname + "/../config/miners.json"); + + fs.writeFile(file, JSON.stringify(miners, null, 4), function (err) { + if (err) { + setTimeout(function() { + self.emit('fileError', { msg: 'Error saving miner configuration', data: err }); + }, 0); + return; + } + + setTimeout(function() { + self.emit('saved', miners); + self.miners = miners; + }, 0); + }); +}; + +MinersService.prototype.poll = function (miner) { + var self = this; + + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.summary().then(function (res) { + _.merge(miner, helpers.parseSummary(miner, res)); + setTimeout(function() { + self.emit('update', miner); + }, 0); + }, function (err) { + setTimeout(function() { + self.emit('error:miner', { miner:miner, error:err }); + }, 0); + }); + + minerClient.devs().then(function (res) { + _.merge(miner, helpers.parseDevDetails(miner, res)); + setTimeout(function() { + self.emit('update', miner); + }, 0); + }, function (err) { + setTimeout(function() { + self.emit('error:miner', { miner:miner, error:err }); + }, 0); + }); + + minerClient.pools().then(function (res) { + _.merge(miner, helpers.parsePools(miner, res)); + setTimeout(function() { + self.emit('update', miner); + }, 0); + }, function (err) { + setTimeout(function() { + self.emit('error:miner', { miner:miner, error:err }); + }, 0); + }); + + minerClient.devdetails().then(function (res) { + _.merge(miner, helpers.parseDevDetails(miner, res)); + + setTimeout(function() { + self.emit('update', miner); + }, 0); + }, function (err) { + setTimeout(function() { + self.emit('error:miner', { miner:miner, error:err }); + }, 0); + }); +}; + +MinersService.prototype.enableGpu = function (miner, gpu) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.gpuenable(gpu.ID).then(function (res) { + if (res.STATUS[0].STATUS == 'I') { + self.emit('success:gpuenable', res.STATUS[0]); + self.poll(miner); + } + }, function (err) { + if (err || err.STATUS[0].STATUS == 'E') { + self.emit('error:gpuenable', err.STATUS[0]); + } + }); +}; + +MinersService.prototype.disableGpu = function (miner, gpu) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.gpudisable(gpu.ID).then(function (res) { + if (res.STATUS[0].STATUS == 'I') { + self.emit('success:gpudisable', res.STATUS[0]); + self.poll(miner); + } + }, function (err) { + if (err || err.STATUS[0].STATUS == 'E') { + self.emit('error:gpuenable', err.STATUS[0]); + } + }); +}; + +MinersService.prototype.updateIntensity = function (miner, device, value) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.gpuintensity(device.ID + ',' + value).then(function (res) { + if (res.STATUS[0].STATUS == 'S' || res.STATUS[0].STATUS == 'I') { + self.emit('success:intensity', device); + self.poll(miner); + } + }, function (err) { + self.emit('error:intensity', err.STATUS[0]); + }); +}; + +MinersService.prototype.updateGpuEngine = function (miner, device, value) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.gpuengine(device.ID + ',' + value).then(function (res) { + if (res.STATUS[0].STATUS == 'S' || res.STATUS[0].STATUS == 'I') { + self.emit('success:gpuengine', device); + self.poll(miner); + } + }, function (err) { + if (data.STATUS[0].STATUS == 'E') { + self.emit('error:gpuengine', err.STATUS[0]); + } + }); +}; + +MinersService.prototype.updateGpuMemory = function (miner, device, value) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.gpumem(device.ID + ',' + value).then(function (res) { + if (res.STATUS[0].STATUS == 'S' || res.STATUS[0].STATUS == 'I') { + self.emit('success:gpumemory', device); + self.poll(miner); + } + }, function (err) { + if (data.STATUS[0].STATUS == 'E') { + self.emit('error:gpumemory', err.STATUS[0]); + } + }); +}; + +MinersService.prototype.updateGpuVoltage = function (miner, device, value) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.gpuvddc(device.ID + ',' + value).then(function (res) { + if (res.STATUS[0].STATUS == 'S' || res.STATUS[0].STATUS == 'I') { + self.emit('success:gpuvoltage', device); + self.poll(miner); + } + }, function (err) { + if (data.STATUS[0].STATUS == 'E') { + self.emit('error:gpuvoltage', data.STATUS[0]); + } + }); +}; + +MinersService.prototype.zeroMiner = function (miner) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + minerClient.zero('all,true').then(function (res) { + self.emit('success:zerominer', { miner: miner, status: res.STATUS[0] }); + }, function (err) { + self.emit('error:zerominer', { miner: miner, status: err.STATUS[0] }); + }); +}; + +MinersService.prototype.changePool = function (miner, pool) { + var self = this; + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + self.miners.forEach(function (m) { + if (m.name === miner.name) { + + minerClient.pools().then(function (data) { + if (data.POOLS && data.POOLS.length > 0) { + var poolId = -1; + + data.POOLS.forEach(function (p) { + if (p.URL == pool.url + ':' + pool.port) { + poolId = p.POOL; + + minerClient.switchpool(poolId).then(function (res) { + self.emit('success:changepool', { miner: miner, pool: pool, status: res.STATUS[0] }); + }, function (err) { + self.emit('error:changepool', { miner: miner, pool: pool, status: err}); + }); + } + }); + + if (poolId == -1) { + // Try add the pool to bf/c/sgminer + minerClient.addpool(pool.url + ':' + pool.port + ',' + pool.workerName + ',' + pool.workerPassword).then(function (res) { + if (res.STATUS && res.STATUS[0].STATUS == 'S') { + self.changePool(miner, pool); + } + }, function (err) { + self.emit('error:changepool', { miner: miner, pool: pool, status: err}); + }); + } + } + }); + } + }); +}; + +MinersService.prototype.ping = function (miner) { + var minerClient = new xgminer(miner.host, miner.port, { miner: miner.miner }); + + return minerClient.version(); +}; + +module.exports = MinersService; + diff --git a/lib/services/PoolsService.js b/lib/services/PoolsService.js new file mode 100644 index 0000000..b0416a3 --- /dev/null +++ b/lib/services/PoolsService.js @@ -0,0 +1,61 @@ +/** + * Dependencies + */ + +var EventEmitter = require('events').EventEmitter, + util = require('util'), + fs = require('fs'), + path = require('path'), + _ = require('lodash'), + helpers = require('../helpers'); + +function PoolsService() { + EventEmitter.call(this); + var self = this; + + if (!self.pools || self.pools.length == 0) { + self.load(); + } +}; + +util.inherits(PoolsService, EventEmitter); + +PoolsService.prototype.load = function () { + var self = this; + + var file = path.normalize(__dirname + "/../config/pools.json"); + + try { + var data = fs.readFileSync(file, 'utf8'); + + this.pools = JSON.parse(data); + + } catch (err) { + setTimeout(function() { + self.emit('fileError', { msg: 'Error reading pool configuration', data: err }); + }, 0); + } +}; + +PoolsService.prototype.save = function (pools) { + var self = this; + + var file = path.normalize(__dirname + "/../config/pools.json"); + + fs.writeFile(file, JSON.stringify(pools, null, 4), function (err) { + if (err) { + setTimeout(function() { + self.emit('fileError', { msg: 'Error saving pool configuration', data: err }); + }, 0); + return; + } + + setTimeout(function() { + self.emit('saved', pools); + self.pools = pools; + }, 0); + }); +}; + +module.exports = PoolsService; + diff --git a/package.json b/package.json index 3a2efe6..c83a0eb 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "express": "~3.4.3", "lodash": "~2.4.1", "socket.io": "~0.9.16", - "xgminer": "~0.2.1" + "xgminer": "~0.2.2" }, "devDependencies": { "grunt": "~0.4.1",