-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test mode, startup refactor, minor test page fixes.
- Use `yarn run test-server` to get a test server with faked-out serial ports that randomly change state over time. Simulated button-mashing. - Reorganizes startup; index.js is a stub that calls server.js. - Adds CORS headers to allow access from anywhere. - Fixes some CSS on the demo page. - Speeds up polling on the demo page to 300ms.
- Loading branch information
Showing
4 changed files
with
192 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,97 +1,4 @@ | ||
'use strict'; | ||
const fs = require('fs'); | ||
const express = require('express'); | ||
// const SerialPort = require('serialport/test'); | ||
const SerialPort = require('serialport'); | ||
// const MockBinding = SerialPort.Binding; // DEBUG | ||
const chalk = require('chalk'); | ||
const cjson = require('cjson'); | ||
const xml2js = require('xml2js'); | ||
const ButtonStatus = require('./lib/buttonStatus'); | ||
const app = express(); | ||
// config.serialPorts.forEach(p => MockBinding.createPort(p)); // DEBUG | ||
|
||
let buttonStatuses = []; | ||
let ports = []; | ||
|
||
if (!fs.existsSync('./config.json')) { | ||
console.warn(chalk.red.bold('Could not find config.json. Please copy' + | ||
' config.json.example to config.json and edit it.')); | ||
process.exit(1); | ||
} | ||
|
||
const config = cjson.load('./config.json'); | ||
|
||
if (!config.serialPorts || !config.serialPorts.length) { | ||
console.warn('No serial ports configured! Please specify serial port(s) in config.json.'); | ||
process.exit(1); | ||
} | ||
|
||
init().catch(console.log); | ||
|
||
async function init() { | ||
let mappings = []; | ||
let portNames = []; | ||
|
||
for (let i = 0; i < config.serialPorts.length; i++) { | ||
let portName = config.serialPorts[i]; | ||
let xml = await readFile(config.mappingFiles[i]).catch(console.error); | ||
let parsedXML = await parseXML(xml).catch(console.error); | ||
mappings.push(parsedXML.ArrayOfInt.int); | ||
portNames.push(portName); | ||
} | ||
|
||
portNames.forEach((portName, portIdx) => { | ||
let port = new SerialPort(portName, { baudrate: 9600 }); | ||
port.on('open', () => { | ||
port.on('data', chunk => { | ||
buttonStatuses[portIdx].update(chunk); | ||
}); | ||
|
||
port.on('error', err => console.warn(`Error: ${err}`)); | ||
|
||
// port.binding.emitData(Buffer.from([0,0,0,0,0,0xFE,0xFE,1])); // DEBUG | ||
}); | ||
|
||
|
||
buttonStatuses.push(new ButtonStatus(mappings[portIdx])); | ||
ports.push(port); | ||
}); | ||
} | ||
|
||
function readFile(file) { | ||
return new Promise((resolve, reject) => { | ||
fs.readFile(file, (err, data) => { | ||
if (err) return reject(err); | ||
resolve(data); | ||
}); | ||
}); | ||
} | ||
|
||
function parseXML(xml) { | ||
return new Promise((resolve, reject) => { | ||
let xmlParser = new xml2js.Parser(); | ||
xmlParser.parseString(xml, (err, data) => { | ||
if (err) return reject(err); | ||
resolve(data); | ||
}); | ||
}); | ||
} | ||
|
||
|
||
app.use('/', express.static('./public')); | ||
|
||
app.get('/buttons', (_req, res) => { | ||
let status = { devices: [] }; | ||
buttonStatuses.forEach((buttonStatus, idx) => { | ||
status.devices[idx] = buttonStatus.toJSON(); | ||
status.devices[idx].name = config.serialPorts[idx]; | ||
}); | ||
res.send(JSON.stringify(status, null, ' ')); | ||
}); | ||
|
||
|
||
let port = process.env.PORT || config.port || 3000; | ||
console.log(chalk.yellow.bold(`Starting server on port ${port}...`)); | ||
console.log(chalk.blue.bold(`Visit http://localhost:${port}/ to see a demo status page.`)); | ||
app.listen(port); | ||
const server = require('./lib/server.js'); | ||
server.start().catch(console.error); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
'use strict'; | ||
const fs = require('fs'); | ||
const express = require('express'); | ||
// const SerialPort = require('serialport/test'); | ||
const SerialPort = require('serialport'); | ||
const chalk = require('chalk'); | ||
const cjson = require('cjson'); | ||
const xml2js = require('xml2js'); | ||
const ButtonStatus = require('./buttonStatus'); | ||
const CONFIG_FILE = process.env.CONFIG_FILE || 'config.json'; | ||
|
||
module.exports = {start}; | ||
|
||
async function start() { | ||
// Read config | ||
// Verify config | ||
// Read mappings | ||
// Create button status instances | ||
// Initialize serial ports | ||
// Bind serial ports to button status instances | ||
// Bind web app to button status instances | ||
|
||
const config = readConfig(CONFIG_FILE); | ||
if (!verifyConfig(config)) process.exit(1); | ||
const mappings = await readMappings(config.mappingFiles); | ||
const buttonStatuses = createButtonStatuses(config.serialPorts.length, mappings); | ||
const ports = openSerialPorts(config.serialPorts); | ||
|
||
for (let i = 0; i < ports.length; i++) { | ||
ports[i].on('data', chunk => buttonStatuses[i].update(chunk)); | ||
} | ||
|
||
startApp(config, buttonStatuses); | ||
} | ||
|
||
function readConfig(file) { | ||
if (!fs.existsSync('./config.json')) { | ||
console.warn(chalk.red.bold(`Could not find ${file}. Please copy` + | ||
` ${file}.example to ${file}) and edit it.`)); | ||
throw new Error(`File not found: ${file}`) | ||
} | ||
|
||
return cjson.load(file); | ||
} | ||
|
||
function verifyConfig(config) { | ||
if (!config.serialPorts || !config.serialPorts.length) { | ||
console.warn(`No serial ports configured! Please specify serial port(s) in ${CONFIG_FILE}.`); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
async function readMappings(mappingFiles) { | ||
const mappings = []; | ||
for (let file of mappingFiles) { | ||
const xml = await readFile(file).catch(console.error); | ||
const parsedXML = await parseXML(xml).catch(console.error); | ||
mappings.push(parsedXML.ArrayOfInt.int); | ||
} | ||
return mappings; | ||
} | ||
|
||
function createButtonStatuses(count, mappings) { | ||
return [...Array(count)].map((_v, idx) => new ButtonStatus(mappings[idx])); | ||
} | ||
|
||
function openSerialPorts(portNames) { | ||
return portNames.map(portName => { | ||
let serial = null; | ||
|
||
if (process.env.TESTMODE) { | ||
const SerialPortTest = require('serialport/test'); | ||
const MockBinding = SerialPortTest.Binding; // DEBUG | ||
MockBinding.createPort(portName); | ||
serial = new SerialPortTest(portName, { baudrate: 9600 }); | ||
// Randomly update button states. | ||
setInterval(() => { | ||
let randomVals = [...Array(7)].map(() => (Math.ceil(Math.random() * 0xFF)) & 0xFE); | ||
randomVals[randomVals.length - 1] += 1; | ||
serial.binding.emitData(Buffer.from(randomVals)); | ||
}, 50); | ||
} else { | ||
serial = new SerialPort(portName, { baudrate: 9600 }); | ||
} | ||
|
||
serial.on('error', err => console.warn(`Error with port ${portName}: ${err}`)); | ||
|
||
return serial; | ||
}); | ||
} | ||
|
||
function readFile(file) { | ||
return new Promise((resolve, reject) => { | ||
fs.readFile(file, (err, data) => { | ||
if (err) return reject(err); | ||
resolve(data); | ||
}); | ||
}); | ||
} | ||
|
||
function parseXML(xml) { | ||
return new Promise((resolve, reject) => { | ||
let xmlParser = new xml2js.Parser(); | ||
xmlParser.parseString(xml, (err, data) => { | ||
if (err) return reject(err); | ||
resolve(data); | ||
}); | ||
}); | ||
} | ||
|
||
function startApp(config, buttonStatuses) { | ||
const app = express(); | ||
app.disable('x-powered-by'); | ||
|
||
// Set proper CORS headers to allow browsers to use this resource from anywhere. | ||
app.use(function(req, res, next) { | ||
res.header("Access-Control-Allow-Origin", "*"); | ||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); | ||
next(); | ||
}); | ||
|
||
app.use('/', express.static('./public')); | ||
|
||
app.get('/buttons', (_req, res) => { | ||
let status = { testMode: !!process.env.TESTMODE, devices: [] }; | ||
buttonStatuses.forEach((buttonStatus, idx) => { | ||
status.devices[idx] = buttonStatus.toJSON(); | ||
status.devices[idx].name = config.serialPorts[idx]; | ||
}); | ||
res.send(JSON.stringify(status, null, ' ')); | ||
}); | ||
|
||
|
||
let port = process.env.PORT || config.port || 3000; | ||
console.log(chalk.yellow.bold(`Starting server on port ${port}...`)); | ||
console.log(chalk.blue.bold(`Visit http://localhost:${port}/ to see a demo status page.`)); | ||
app.listen(port); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters