Adding Project Files
This commit is contained in:
170
arduino_thread.js
Normal file
170
arduino_thread.js
Normal file
@@ -0,0 +1,170 @@
|
||||
const { SerialPort } = require('serialport');
|
||||
const fs = require('fs');
|
||||
const EventEmitter = require('events');
|
||||
// add parser import
|
||||
const { ReadlineParser } = require('@serialport/parser-readline');
|
||||
|
||||
class ArduinoThread extends EventEmitter {
|
||||
constructor(comPort, baudRate = 9600, sliderCount = 3, yamlPath = 'config.json', verbose = false) {
|
||||
super();
|
||||
|
||||
if (typeof comPort !== 'string') throw new Error('comPort must be a string');
|
||||
if (typeof baudRate !== 'number') throw new Error('baudRate must be a number');
|
||||
if (typeof sliderCount !== 'number') throw new Error('sliderCount must be a number');
|
||||
|
||||
this.port = new SerialPort({ path: comPort, baudRate, autoOpen: true });
|
||||
this.sliderCount = sliderCount;
|
||||
this.yamlPath = yamlPath; // now points to config.json by default
|
||||
this.verbose = !!verbose;
|
||||
|
||||
this.sendBuffer = [];
|
||||
this.updateList = Array(sliderCount).fill(0);
|
||||
this.isRunning = true;
|
||||
this.pendingMessage = null;
|
||||
|
||||
// pipe through a newline-based parser so we always get full lines
|
||||
try {
|
||||
this.parser = this.port.pipe(new ReadlineParser({ delimiter: '\n' }));
|
||||
this.parser.on('data', (line) => this._handleData(line));
|
||||
} catch (err) {
|
||||
// fallback to raw data event if parser unavailable
|
||||
this.port.on('data', (data) => this._handleData(data));
|
||||
}
|
||||
|
||||
// keep error/close handlers on the port
|
||||
this.port.on('error', (err) => {
|
||||
this.emit('error', err);
|
||||
if (this.verbose) console.error('Serial error:', err);
|
||||
});
|
||||
this.port.on('close', () => {
|
||||
this.isRunning = false;
|
||||
if (this.verbose) console.log('Serial port closed');
|
||||
});
|
||||
}
|
||||
|
||||
_handleData(data) {
|
||||
// accept string or Buffer; trim newline/whitespace
|
||||
const input = (typeof data === 'string') ? data.trim() : data.toString().trim();
|
||||
|
||||
// acknowledgement handling
|
||||
if (input === 'OK' && this.pendingMessage) {
|
||||
if (this.verbose) console.log(`Message confirmed: ${this.pendingMessage}`);
|
||||
this.pendingMessage = null;
|
||||
// drain buffer if present
|
||||
if (this.sendBuffer.length > 0) {
|
||||
const next = this.sendBuffer.shift();
|
||||
this._writeMessage(next);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const numbers = input.split('|');
|
||||
if (numbers.length === this.sliderCount) {
|
||||
try {
|
||||
const values = numbers.map((n) => parseInt(n, 10));
|
||||
values.forEach((val, i) => {
|
||||
if (!Number.isFinite(val)) throw new Error('parse error');
|
||||
if (val !== this.updateList[i]) {
|
||||
this._setVolume(val, i);
|
||||
}
|
||||
});
|
||||
this.updateList = values;
|
||||
this.emit('update', values); // notify listeners (GUI)
|
||||
if (this.verbose) console.log('Slider update ->', values);
|
||||
} catch (err) {
|
||||
this.emit('warn', { msg: 'Invalid slider data', raw: input });
|
||||
if (this.verbose) console.warn('Invalid slider data:', input);
|
||||
}
|
||||
} else {
|
||||
// Not an update; emit for consumers
|
||||
this.emit('raw', input);
|
||||
if (this.verbose) console.warn('Unexpected input:', input);
|
||||
}
|
||||
}
|
||||
|
||||
send(message) {
|
||||
if (!message || typeof message !== 'string') return;
|
||||
if (this.pendingMessage) {
|
||||
// queue if another message is awaiting ACK
|
||||
this.sendBuffer.push(message);
|
||||
return;
|
||||
}
|
||||
this._writeMessage(message);
|
||||
}
|
||||
|
||||
_writeMessage(message) {
|
||||
if (!this.port || !this.port.writable) {
|
||||
const err = new Error('Serial port not writable');
|
||||
this.emit('error', err);
|
||||
if (this.verbose) console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingMessage = message;
|
||||
this.port.write(message + '\n', (err) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
if (this.verbose) console.error('Error writing:', err.message);
|
||||
this.pendingMessage = null;
|
||||
} else if (this.verbose) {
|
||||
console.log('Wrote message:', message);
|
||||
}
|
||||
});
|
||||
|
||||
// fallback timeout if device never replies OK
|
||||
setTimeout(() => {
|
||||
if (this.pendingMessage) {
|
||||
this.emit('warn', { msg: "Did not receive OK from device", message: this.pendingMessage });
|
||||
if (this.verbose) console.warn('Did not receive OK from device for', this.pendingMessage);
|
||||
this.pendingMessage = null;
|
||||
// try to drain next queued message if any
|
||||
if (this.sendBuffer.length > 0) {
|
||||
const next = this.sendBuffer.shift();
|
||||
this._writeMessage(next);
|
||||
}
|
||||
}
|
||||
}, 100); // adjustable timeout
|
||||
}
|
||||
|
||||
_setVolume(value, sliderNumber) {
|
||||
const normalized = value / 1023;
|
||||
|
||||
if (!fs.existsSync(this.yamlPath)) {
|
||||
this.emit('warn', { msg: 'Config file missing', path: this.yamlPath });
|
||||
if (this.verbose) console.warn('Config file missing:', this.yamlPath);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const file = fs.readFileSync(this.yamlPath, 'utf8');
|
||||
const config = JSON.parse(file || '{}'); // parse JSON config.json
|
||||
const processes = config[sliderNumber] || [];
|
||||
|
||||
processes.forEach((proc) => {
|
||||
// Emit an event so the application can perform the platform-specific change.
|
||||
this.emit('set-volume', { process: proc, value: normalized, slider: sliderNumber });
|
||||
if (this.verbose) {
|
||||
console.log(`Would set volume for "${proc}" to ${normalized.toFixed(2)} (slider ${sliderNumber})`);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
if (this.verbose) console.error('Config JSON error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.isRunning = false;
|
||||
try {
|
||||
if (this.port && this.port.isOpen) this.port.close();
|
||||
} catch (e) {
|
||||
if (this.verbose) console.warn('Error closing port', e);
|
||||
}
|
||||
}
|
||||
|
||||
getValues() {
|
||||
return this.updateList.slice();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ArduinoThread;
|
||||
Reference in New Issue
Block a user