feat: send data to and from serial port

This commit is contained in:
Vlad Stan 2022-07-13 17:22:45 +03:00
parent ae68c4dc6e
commit 7594474189
3 changed files with 184 additions and 79 deletions

View file

@ -33,6 +33,16 @@ new Vue({
show: false show: false
}, },
serial: {
selectedPort: null,
writableStreamClosed: null,
writer: null,
readableStreamClosed: null,
reader: null,
showAdvancedConfig: false,
config: {}
},
formDialog: { formDialog: {
show: false, show: false,
data: {} data: {}
@ -500,8 +510,7 @@ new Vue({
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
} }
}, },
sharePsbtOnSerialPort: async function () { checkSerialPortSupported: function () {
console.log('### sharePsbtOnSerialPort', navigator.serial, navigator)
if (!navigator.serial) { if (!navigator.serial) {
this.$q.notify({ this.$q.notify({
type: 'warning', type: 'warning',
@ -510,46 +519,119 @@ new Vue({
'Make sure your browser supports Serial Port and that you are using HTTPS.', 'Make sure your browser supports Serial Port and that you are using HTTPS.',
timeout: 10000 timeout: 10000
}) })
return return false
} }
navigator.serial.addEventListener('connect', event => { return true
console.log('### navigator.serial event: connected!', event) },
}) openSerialPort: async function () {
if (!this.checkSerialPortSupported()) return
navigator.serial.addEventListener('disconnect', event => { console.log('### openSerialPort', this.serial.selectedPort)
console.log('### navigator.serial event: disconnected!', event)
})
try { try {
// const ports = await navigator.serial.getPorts(); navigator.serial.addEventListener('connect', event => {
const port = await navigator.serial.requestPort() console.log('### navigator.serial event: connected!', event)
console.log('### port', port) })
navigator.serial.addEventListener('disconnect', event => {
console.log('### navigator.serial event: disconnected!', event)
})
this.serial.selectedPort = await navigator.serial.requestPort()
// Wait for the serial port to open. // Wait for the serial port to open.
await port.open({baudRate: 9600}) await this.serial.selectedPort.open({baudRate: 9600})
this.startSerialPortReading()
const writer = port.writable.getWriter() const textEncoder = new TextEncoderStream()
this.serial.writableStreamClosed = textEncoder.readable.pipeTo(
const psbtByteArray = Uint8Array.from( this.serial.selectedPort.writable
atob(this.payment.psbtBase64),
c => c.charCodeAt(0)
) )
await writer.write(psbtByteArray)
// Allow the serial port to be closed later. this.serial.writer = textEncoder.writable.getWriter()
writer.releaseLock()
await port.close()
console.log('### sharePsbtOnSerialPort done')
} catch (error) { } catch (error) {
console.log('### error', error)
this.$q.notify({ this.$q.notify({
type: 'warning', type: 'warning',
message: 'Serial port communication failed!', message: 'Cannot open serial port!',
caption: `${error}`, caption: `${error}`,
timeout: 10000 timeout: 10000
}) })
} }
}, },
closeSerialPort: async function () {
try {
console.log('### closeSerialPort', this.serial.selectedPort)
if (this.serial.writer) this.serial.writer.close()
if (this.serial.writableStreamClosed)
await this.serial.writableStreamClosed
if (this.serial.reader) this.reader.writer.close()
if (this.serial.readableStreamClosed)
await this.serial.readableStreamClosed
if (this.serial.selectedPort) await this.serial.selectedPort.close()
this.serial.selectedPort = null
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Cannot close serial port!',
caption: `${error}`,
timeout: 10000
})
}
},
sendPsbtToSerialPort: async function () {
try {
await this.serial.writer.write(this.payment.psbtBase64 + '\n')
this.$q.notify({
type: 'positive',
message: 'Data sent to serial port!',
timeout: 5000
})
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to send data to serial port!',
caption: `${error}`,
timeout: 10000
})
}
},
startSerialPortReading: async function () {
const port = this.serial.selectedPort
while (port && port.readable) {
const textDecoder = new TextDecoderStream()
this.serial.readableStreamClosed = port.readable.pipeTo(
textDecoder.writable
)
this.serial.reader = textDecoder.readable.getReader()
try {
while (true) {
console.log('### reader.read()')
const {value, done} = await this.serial.reader.read()
if (value) {
console.log(value)
this.$q.notify({
type: 'warning',
message: 'Received data from serial port (not psbt)',
caption: value.slice(0, 80) + '...',
timeout: 5000
})
}
if (done) {
this.serial.reader.close()
this.serial.readereadableStreamClosed()
return
}
}
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Serial port communication error!',
caption: `${error}`,
timeout: 10000
})
}
}
console.log('### startSerialPortReading DONE')
},
sharePsbtWithAnimatedQRCode: async function () { sharePsbtWithAnimatedQRCode: async function () {
console.log('### sharePsbtWithAnimatedQRCode') console.log('### sharePsbtWithAnimatedQRCode')
}, },

View file

@ -260,6 +260,7 @@ const tableData = {
fee: 0, fee: 0,
txSize: 0, txSize: 0,
psbtBase64: '', psbtBase64: '',
psbtBase64Signed: '',
utxoSelectionModes: [ utxoSelectionModes: [
'Manual', 'Manual',
'Random', 'Random',
@ -268,6 +269,18 @@ const tableData = {
'Larger Inputs First' 'Larger Inputs First'
], ],
utxoSelectionMode: 'Manual', utxoSelectionMode: 'Manual',
signModes: [
{
label: 'Serial Port',
value: 'serial-port'
},
{
label: 'Animated QR',
value: 'animated-qr',
disable: true
}
],
signMode: '',
show: false, show: false,
showAdvanced: false showAdvanced: false
}, },

View file

@ -1009,72 +1009,82 @@
</div> </div>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col-4 q-pr-lg"> <div class="col-3 q-pr-lg">
<q-btn unelevated color="secondary" type="submit" <q-btn unelevated color="secondary" type="submit"
>Create PSBT</q-btn >Create PSBT</q-btn
> >
</div> </div>
<div class="col-8"> <div class="col-9">
<q-btn-dropdown <q-input
v-if="payment.psbtBase64" v-if="payment.psbtBase64"
split v-model="payment.psbtBase64"
class="float-right" filled
color="secondary" readonly
label="Share PSBT" />
@click="sharePsbtOnSerialPort"
>
<q-list>
<q-item
@click="sharePsbtOnSerialPort"
clickable
v-close-popup
>
<q-item-section avatar>
<q-avatar
icon="usb"
color="primary"
text-color="white"
/>
</q-item-section>
<q-item-section>
<q-item-label>Serial Port</q-item-label>
<q-item-label caption
>Send the PSBT using an USB port
</q-item-label>
</q-item-section>
</q-item>
<q-item
@click="sharePsbtWithAnimatedQRCode"
disabled
v-close-popup
>
<q-item-section avatar>
<q-avatar
icon="qr_code"
color="secondary"
text-color="white"
/>
</q-item-section>
<q-item-section>
<q-item-label>Animated QR Code</q-item-label>
<q-item-label caption>Comming Soon</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div> </div>
</div> </div>
<div <div
v-if="payment.psbtBase64" v-if="payment.psbtBase64"
class="row items-center no-wrap q-mb-md" class="row items-center no-wrap q-mb-md"
> >
<div class="col-12"> <div class="col-3">Sign With</div>
<div class="col-9">
<q-option-group
v-model="payment.signMode"
:options="payment.signModes"
color="primary"
label="Sign Mode"
inline
></q-option-group>
</div>
</div>
<q-separator></q-separator>
<div
v-if="payment.psbtBase64 && payment.signMode === 'serial-port'"
class="row items-center no-wrap q-mb-md q-mt-lg"
>
<div class="col-3"></div>
<div class="col-3">
<q-btn
v-if="!serial.selectedPort"
@click="openSerialPort()"
unelevated
color="secondary"
>Connect</q-btn
>
<q-btn
v-if="serial.selectedPort"
@click="closeSerialPort()"
outline
color="gray"
>Disconnect</q-btn
>
</div>
<div class="col-3">
<q-btn
v-if="serial.selectedPort"
@click="sendPsbtToSerialPort()"
unelevated
color="secondary float-right"
>Send PSBT</q-btn
>
</div>
<div class="col-3">
<q-toggle
label="Advanced Config"
color="secodary float-right"
v-model="serial.showAdvancedConfig"
></q-toggle>
</div>
</div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-4 q-pr-lg"></div>
<div class="col-8">
<q-input <q-input
v-model="payment.psbtBase64" v-if="payment.psbtBase64Signed"
v-model="payment.psbtBase64Signed"
filled filled
readonly readonly
type="textarea"
/> />
</div> </div>
</div> </div>