Skip to content

Commit

Permalink
fix: improve the options and output of terminal (#1962)
Browse files Browse the repository at this point in the history
- improve output so CR always print as NL
- change --port to --path
- change --echo to --no-echo (default is to echo)
- fix double echo output when selecting the port from a list
  • Loading branch information
reconbot authored Oct 27, 2019
1 parent 61e9d9e commit 4b23928
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 79 deletions.
145 changes: 68 additions & 77 deletions packages/terminal/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,116 +1,107 @@
#!/usr/bin/env node

// #!/usr/bin/env node
const { Select } = require('enquirer')
const args = require('commander')
const SerialPort = require('serialport')
const { version } = require('../package.json')
const args = require('commander')
const PromptList = require('prompt-list')
const { OutputTranslator } = require('./output-translator')

function makeNumber(input) {
return Number(input)
}
const makeNumber = input => Number(input)

args
.version(version)
.usage('[options]')
.description('A basic terminal interface for communicating over a serial port. Pressing ctrl+c exits.')
.option('-l --list', 'List available ports then exit')
.option('-p, --port <port>', 'Path or Name of serial port')
.option('-p, --path <path>', 'Path of the serial port')
.option('-b, --baud <baudrate>', 'Baud rate default: 9600', makeNumber, 9600)
.option('--databits <databits>', 'Data bits default: 8', makeNumber, 8)
.option('--parity <parity>', 'Parity default: none', 'none')
.option('--stopbits <bits>', 'Stop bits default: 1', makeNumber, 1)
// TODO make this on by default
.option('--echo --localecho', 'Print characters as you type them.')
.option('--no-echo', "Don't print characters as you type them.")
.parse(process.argv)

function logErrorAndExit(error) {
console.error(error)
process.exit(1)
}

function listPorts() {
SerialPort.list().then(
ports => {
ports.forEach(port => {
console.log(`${port.path}\t${port.pnpId || ''}\t${port.manufacturer || ''}`)
})
},
err => {
console.error('Error listing ports', err)
}
)
const listPorts = async () => {
const ports = await SerialPort.list()
for (port of ports) {
console.log(`${port.path}\t${port.pnpId || ''}\t${port.manufacturer || ''}`)
}
}

function askForPort() {
return SerialPort.list().then(ports => {
if (ports.length === 0) {
console.error('No ports detected and none specified')
process.exit(2)
}

const portSelection = new PromptList({
name: 'serial-port-selection',
message: 'Select a serial port to open',
choices: ports.map((port, i) => ({
value: `[${i + 1}]\t${port.path}\t${port.pnpId || ''}\t${port.manufacturer || ''}`,
name: port.path,
})),
validate: Boolean, // ensure we picked something
})
const askForPort = async () => {
const ports = await SerialPort.list()
if (ports.length === 0) {
console.error('No ports detected and none specified')
process.exit(2)
}

return portSelection.run().then(answer => {
console.log(`Opening serial port: ${answer}`)
return answer
})
})
const answer = await new Select({
name: 'serial-port-selection',
message: 'Select a serial port to open',
choices: ports.map((port, i) => ({
value: `[${i + 1}]\t${port.path}\t${port.pnpId || ''}\t${port.manufacturer || ''}`,
name: port.path,
})),
required: true,
}).run()
return answer
}

function createPort(selectedPort) {
const createPort = path => {
console.log(`Opening serial port: ${path} echo: ${args.echo}`)

const openOptions = {
baudRate: args.baud,
dataBits: args.databits,
parity: args.parity,
stopBits: args.stopbits,
}

const port = new SerialPort(selectedPort, openOptions)

process.stdin.resume()
process.stdin.setRawMode(true)
process.stdin.on('data', s => {
if (s[0] === 0x03) {
port.close()
process.exit(0)
}
if (args.localecho) {
if (s[0] === 0x0d) {
process.stdout.write('\n')
} else {
process.stdout.write(s)
}
}
port.write(s)
})

port.on('data', data => {
process.stdout.write(data.toString())
})
const port = new SerialPort(path, openOptions)
const output = new OutputTranslator()
output.pipe(process.stdout)
port.pipe(output)

port.on('error', err => {
console.log('Error', err)
console.error('Error', err)
process.exit(1)
})

port.on('close', err => {
console.log('Closed', err)
process.exit(err ? 1 : 0)
})
process.stdin.setRawMode(true)
process.stdin.on('data', input => {
for (const byte of input) {
// ctrl+c
if (byte === 0x03) {
port.close()
process.exit(0)
}
}
port.write(input)
if (args.echo) {
output.write(input)
}
})
process.stdin.resume()

process.stdin.on('end', () => {
port.close()
process.exit(0)
})
}

if (args.list) {
listPorts()
} else {
Promise.resolve(args.port || askForPort())
.then(createPort)
.catch(logErrorAndExit)
const run = async () => {
if (args.list) {
listPorts()
return
}
const path = args.path || (await askForPort())
await createPort(path)
}

run().catch(error => {
console.error(error)
process.exit(1)
})
18 changes: 18 additions & 0 deletions packages/terminal/lib/output-translator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { Transform } = require('stream')

/**
* Convert carriage returns to newlines for output
*/
class OutputTranslator extends Transform {
_transform(chunk, _encoding, cb) {
for (let index = 0; index < chunk.length; index++) {
const byte = chunk[index]
if (byte === 0x0d) {
chunk[index] = 0x0a
}
}
this.push(chunk)
cb()
}
}
module.exports.OutputTranslator = OutputTranslator
4 changes: 2 additions & 2 deletions packages/terminal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"serialport-terminal": "./lib/index.js"
},
"dependencies": {
"commander": "^2.13.0",
"prompt-list": "^3.2.0",
"commander": "^3.0.2",
"enquirer": "^2.3.2",
"serialport": "^8.0.3"
},
"engines": {
Expand Down

0 comments on commit 4b23928

Please sign in to comment.