Adding Project Files

This commit is contained in:
2025-11-17 13:53:14 +01:00
parent 7c7f8ce18f
commit 7c94b4322c
11 changed files with 8189 additions and 0 deletions

170
arduino_thread.js Normal file
View 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;