219 lines
5.4 KiB
JavaScript
219 lines
5.4 KiB
JavaScript
|
import { SerialPort } from 'serialport'
|
||
|
import { autoDetect } from '@serialport/bindings-cpp'
|
||
|
import { effect, signal } from '@preact/signals-core'
|
||
|
|
||
|
class SerialManager {
|
||
|
constructor() {
|
||
|
this.serial = signal(null)
|
||
|
this.currentDisplay = signal({
|
||
|
first: 'Filadelfia streamer ',
|
||
|
second: ' ',
|
||
|
})
|
||
|
this.queue = null
|
||
|
this.attempting = null
|
||
|
this.data = ''
|
||
|
this.failues = 0
|
||
|
// this.serial = null
|
||
|
this.gotPing = false
|
||
|
this.logHistory = []
|
||
|
this.setup()
|
||
|
}
|
||
|
|
||
|
setup() {
|
||
|
this.binding = autoDetect()
|
||
|
setInterval(() => {
|
||
|
if (!this.serial.value) return
|
||
|
this.serial.value.write('~')
|
||
|
}, 15000)
|
||
|
|
||
|
effect(() => {
|
||
|
if (!this.serial.value) {
|
||
|
this.gotPing = false
|
||
|
this.displayUpdated = false
|
||
|
this.safeStartDelay(5)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
this.log('info', `Connection with display established.`)
|
||
|
this.updateDisplay(
|
||
|
'Filadelfia streamer',
|
||
|
'Version ' + this.core.version,
|
||
|
true
|
||
|
)
|
||
|
|
||
|
if (this.queue) {
|
||
|
setTimeout(() => {
|
||
|
this.updateDisplay(
|
||
|
this.queue[0],
|
||
|
this.queue[1],
|
||
|
)
|
||
|
}, 1000)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
effect(() => {
|
||
|
let display = this.currentDisplay.value
|
||
|
this.io?.io.emit('serial.display', display)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
init(core, io) {
|
||
|
this.core = core
|
||
|
this.io = io
|
||
|
this.safeStartDelay(1)
|
||
|
}
|
||
|
|
||
|
log(level, message) {
|
||
|
this.logHistory.unshift(`[${(new Date()).toISOString().replace('T', ' ').split('.')[0]}] ${message}`)
|
||
|
this.logHistory = this.logHistory.slice(0, 40)
|
||
|
this.core.log[level]('DISPLAY: ' + message)
|
||
|
this.io.io.emit('serial.status', this.status())
|
||
|
}
|
||
|
|
||
|
bufferToString(buf) {
|
||
|
if (typeof buf === 'string') return buf
|
||
|
let out = Buffer.from(buf)
|
||
|
for (let i = 0; i < out.length; i++) {
|
||
|
if (out[i] === 255) {
|
||
|
out.writeUInt8('#'.charCodeAt(0), i)
|
||
|
}
|
||
|
}
|
||
|
return out.toString().replace(/#/g, '█')
|
||
|
}
|
||
|
|
||
|
updateDisplay(firstLine, secondLine, local = false) {
|
||
|
if (!local) {
|
||
|
this.queue = [firstLine, secondLine]
|
||
|
}
|
||
|
if (!this.serial.value) return
|
||
|
let first = Buffer.from(firstLine)
|
||
|
let second = Buffer.from(secondLine)
|
||
|
if (first.length > 20) {
|
||
|
first = first.subarray(0, 20)
|
||
|
} else if (first.length < 20) {
|
||
|
first = Buffer.concat([first, Buffer.from(new Array(20 - first.length).fill(32))])
|
||
|
}
|
||
|
if (second.length > 20) {
|
||
|
second = second.subarray(0, 20)
|
||
|
} else if (second.length < 20) {
|
||
|
second = Buffer.concat([second, Buffer.from(new Array(20 - second.length).fill(32))])
|
||
|
}
|
||
|
|
||
|
this.serial.value.write(Buffer.concat([
|
||
|
Buffer.from('^'),
|
||
|
first,
|
||
|
second,
|
||
|
]))
|
||
|
this.currentDisplay.value = {
|
||
|
first: this.bufferToString(firstLine).slice(0, 20),
|
||
|
second: this.bufferToString(secondLine).slice(0, 20),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
safeStartDelay(sec) {
|
||
|
if (this.attempting || !this.core) return
|
||
|
|
||
|
this.log('info', `Attempting to connect with display in ${sec} second${sec > 1 ? 's': ''}.`)
|
||
|
this.attempting = setTimeout(this.safeStart.bind(this), sec * 1000)
|
||
|
}
|
||
|
|
||
|
safeStart() {
|
||
|
this.start()
|
||
|
.then(
|
||
|
serial => {
|
||
|
this.attempting = null
|
||
|
this.failues = 0
|
||
|
this.serial.value = serial
|
||
|
},
|
||
|
err => {
|
||
|
this.attempting = null
|
||
|
this.failues = Math.min(this.failues + 1, 4)
|
||
|
this.log('error', err.message)
|
||
|
this.safeStartDelay(this.failues * 15)
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
|
||
|
async start() {
|
||
|
let list = await this.binding.list()
|
||
|
if (!list.length) {
|
||
|
throw new Error('No com ports were found')
|
||
|
}
|
||
|
for (let item of list) {
|
||
|
this.log('info', `Found ${item.path} (${item.friendlyName})`)
|
||
|
}
|
||
|
for (let item of list) {
|
||
|
this.log('info', `Testing out ${item.path} (${item.friendlyName})`)
|
||
|
let serial = new SerialPort({
|
||
|
path: item.path,
|
||
|
baudRate: 9600,
|
||
|
autoOpen: false,
|
||
|
})
|
||
|
|
||
|
await this.openSerial(serial)
|
||
|
if (!serial.port) continue
|
||
|
|
||
|
this.listen(serial)
|
||
|
let index = 0
|
||
|
while (index < 50 && !this.gotPing) {
|
||
|
if (index % 10 === 0) serial.write('~')
|
||
|
await new Promise(res => setTimeout(res, 100))
|
||
|
index++
|
||
|
}
|
||
|
if (this.gotPing) return serial
|
||
|
serial.destroy()
|
||
|
}
|
||
|
throw new Error('Failed to find display device')
|
||
|
}
|
||
|
|
||
|
listen(serial) {
|
||
|
serial.on('data', this.onData.bind(this))
|
||
|
serial.on('error', err => {
|
||
|
this.log('error', 'serial on error: ' + err.message)
|
||
|
})
|
||
|
serial.once('close', () => {
|
||
|
serial.destroy()
|
||
|
if (serial === this.serial.value) {
|
||
|
this.log('warn', 'Serial was closed')
|
||
|
this.serial.value = null
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
onData(data) {
|
||
|
this.data = (this.data + data).slice(-1000)
|
||
|
|
||
|
if (this.data.indexOf('^') < 0) return
|
||
|
|
||
|
let split = this.data.split('^')
|
||
|
this.data = split[split.length - 1]
|
||
|
this.gotPing = this.gotPing || split.includes('pong')
|
||
|
}
|
||
|
|
||
|
openSerial(serial) {
|
||
|
return new Promise(res => {
|
||
|
serial.open(err => {
|
||
|
if (err) {
|
||
|
this.log('error', 'Failed to open serial connection: ' + err.message)
|
||
|
serial.destroy()
|
||
|
}
|
||
|
res()
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
getDisplay() {
|
||
|
return this.currentDisplay.value
|
||
|
}
|
||
|
|
||
|
status() {
|
||
|
return {
|
||
|
running: Boolean(this.serial.value),
|
||
|
log: this.logHistory.join('\n'),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const serial = new SerialManager()
|
||
|
export default serial;
|