added hex serial input/output modes

This commit is contained in:
Eugene Pankov 2021-03-14 11:34:58 +01:00
parent 3676b90c9f
commit 36f77c5b63
No known key found for this signature in database
GPG key ID: 5896FCBBDD1CF4F4
6 changed files with 89 additions and 13 deletions

View file

@ -20,8 +20,10 @@
"@types/node": "14.14.14",
"@types/ssh2": "^0.5.35",
"ansi-colors": "^4.1.1",
"binstring": "^0.2.1",
"buffer-replace": "^1.0.0",
"cli-spinner": "^0.2.10"
"cli-spinner": "^0.2.10",
"hexer": "^1.5.0"
},
"peerDependencies": {
"@angular/animations": "^9.1.9",

View file

@ -1,3 +1,6 @@
import hexdump from 'hexer'
import colors from 'ansi-colors'
import binstring from 'binstring'
import stripAnsi from 'strip-ansi'
import bufferReplace from 'buffer-replace'
import { BaseSession } from 'terminus-terminal'
@ -30,6 +33,7 @@ export interface SerialConnection {
color?: string
inputMode?: InputMode
inputNewlines?: NewlineMode
outputMode?: OutputMode
outputNewlines?: NewlineMode
}
@ -42,7 +46,8 @@ export interface SerialPortInfo {
description?: string
}
export type InputMode = null | 'readline' // eslint-disable-line @typescript-eslint/no-type-alias
export type InputMode = null | 'readline' | 'readline-hex' // eslint-disable-line @typescript-eslint/no-type-alias
export type OutputMode = null | 'hex' // eslint-disable-line @typescript-eslint/no-type-alias
export type NewlineMode = null | 'cr' | 'lf' | 'crlf' // eslint-disable-line @typescript-eslint/no-type-alias
export class SerialSession extends BaseSession {
@ -67,14 +72,14 @@ export class SerialSession extends BaseSession {
input: this.inputReadlineInStream,
output: this.inputReadlineOutStream,
terminal: true,
prompt: this.connection.inputMode === 'readline-hex' ? 'hex> ' : '> ',
} as any)
this.inputReadlineOutStream.on('data', data => {
if (this.connection.inputMode === 'readline') {
this.emitOutput(data)
}
this.emitOutput(Buffer.from(data))
})
this.inputReadline.on('line', line => {
this.onInput(new Buffer(line + '\n'))
this.resetInputPrompt()
})
this.output$.pipe(debounce(() => interval(500))).subscribe(() => this.onOutputSettled())
}
@ -97,7 +102,7 @@ export class SerialSession extends BaseSession {
}
write (data: Buffer): void {
if (this.connection.inputMode === 'readline') {
if (this.connection.inputMode?.startsWith('readline')) {
this.inputReadlineInStream.write(data)
} else {
this.onInput(data)
@ -156,6 +161,16 @@ export class SerialSession extends BaseSession {
}
private onInput (data: Buffer) {
if (this.connection.inputMode === 'readline-hex') {
const tokens = data.toString().split(/\s/g)
data = Buffer.concat(tokens.filter(t => !!t).map(t => {
if (t.startsWith('0x')) {
t = t.substring(2)
}
return binstring(t, { 'in': 'hex' })
}))
}
data = this.replaceNewlines(data, this.connection.inputNewlines)
if (this.serial) {
this.serial.write(data.toString())
@ -163,7 +178,7 @@ export class SerialSession extends BaseSession {
}
private onOutputSettled () {
if (this.connection.inputMode === 'readline' && !this.inputPromptVisible) {
if (this.connection.inputMode?.startsWith('readline') && !this.inputPromptVisible) {
this.resetInputPrompt()
}
}
@ -177,7 +192,7 @@ export class SerialSession extends BaseSession {
private onOutput (data: Buffer) {
const dataString = data.toString()
if (this.connection.inputMode === 'readline') {
if (this.connection.inputMode?.startsWith('readline')) {
if (this.inputPromptVisible) {
clearLine(this.inputReadlineOutStream, 0)
this.inputPromptVisible = false
@ -185,7 +200,21 @@ export class SerialSession extends BaseSession {
}
data = this.replaceNewlines(data, this.connection.outputNewlines)
this.emitOutput(data)
if (this.connection.outputMode === 'hex') {
this.emitOutput(Buffer.concat([
new Buffer('\r\n'),
Buffer.from(hexdump(data, {
group: 1,
gutter: 4,
divide: colors.gray(' '),
emptyHuman: colors.gray(''),
}).replace(/\n/g, '\r\n')),
new Buffer('\r\n\n'),
]))
} else {
this.emitOutput(data)
}
if (this.scripts) {
let found = false

View file

@ -62,19 +62,19 @@
.row
.col-6
//- .form-line
.form-line
.header
.title Output mode
.d-flex(ngbDropdown)
button.btn.btn-secondary.btn-tab-bar(
ngbDropdownToggle,
) {{getInputModeName(connection.inputMode)}}
) {{getOutputModeName(connection.outputMode)}}
div(ngbDropdownMenu)
a.d-flex.flex-column(
*ngFor='let mode of inputModes',
(click)='connection.inputMode = mode.key',
*ngFor='let mode of outputModes',
(click)='connection.outputMode = mode.key',
ngbDropdownItem
)
div {{mode.name}}

View file

@ -18,6 +18,11 @@ export class EditConnectionModalComponent {
inputModes = [
{ key: null, name: 'Normal', description: 'Input is sent as you type' },
{ key: 'readline', name: 'Line by line', description: 'Line editor, input is sent after you press Enter' },
{ key: 'readline-hex', name: 'Hexadecimal', description: 'Send bytes by typing in hex values' },
]
outputModes = [
{ key: null, name: 'Normal', description: 'Output is shown as it is received' },
{ key: 'hex', name: 'Hexadecimal', description: 'Output is shown as a hexdump' },
]
newlineModes = [
{ key: null, name: 'Keep' },
@ -39,6 +44,10 @@ export class EditConnectionModalComponent {
return this.inputModes.find(x => x.key === key)?.name
}
getOutputModeName (key) {
return this.outputModes.find(x => x.key === key)?.name
}
portsAutocomplete = text$ => text$.pipe(map(() => {
return this.foundPorts.map(x => x.name)
}))

View file

@ -35,6 +35,7 @@ export class SerialSettingsTabComponent {
xoff: false,
xon: false,
inputMode: null,
outputMode: null,
inputNewlines: null,
outputNewlines: null,
}

View file

@ -27,11 +27,21 @@
"@types/node" "*"
"@types/ssh2-streams" "*"
ansi-color@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/ansi-color/-/ansi-color-0.2.1.tgz#3e75c037475217544ed763a8db5709fa9ae5bf9a"
integrity sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o=
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
binstring@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/binstring/-/binstring-0.2.1.tgz#8a174d301f6d54efda550dd98bb4cb524eacd75d"
integrity sha1-ihdNMB9tVO/aVQ3Zi7TLUk6s110=
buffer-replace@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-replace/-/buffer-replace-1.0.0.tgz#bc427c40af4c1f06d6933dede57110acba8ade54"
@ -41,3 +51,28 @@ cli-spinner@^0.2.10:
version "0.2.10"
resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47"
integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==
hexer@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/hexer/-/hexer-1.5.0.tgz#b86ce808598e8a9d1892c571f3cedd86fc9f0653"
integrity sha1-uGzoCFmOip0YksVx887dhvyfBlM=
dependencies:
ansi-color "^0.2.1"
minimist "^1.1.0"
process "^0.10.0"
xtend "^4.0.0"
minimist@^1.1.0:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
process@^0.10.0:
version "0.10.1"
resolved "https://registry.yarnpkg.com/process/-/process-0.10.1.tgz#842457cc51cfed72dc775afeeafb8c6034372725"
integrity sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==