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;