First commit

This commit is contained in:
2024-09-13 09:59:14 +02:00
parent ad6781e737
commit 1d93632f6d
3 changed files with 337 additions and 2 deletions

211
src/modbus-rtu-master.js Normal file
View File

@@ -0,0 +1,211 @@
class ModbusRTUMaster {
constructor(config = {}) {
this.port = null;
this.reader = null;
this.writer = null;
this.baudRate = config.baudRate || 9600;
this.dataBits = config.dataBits || 8;
this.stopBits = config.stopBits || 1;
this.parity = config.parity || 'none';
this.flowControl = config.flowControl || 'none';
this.timeout = config.timeout || 2000; // Default timeout is 2 seconds
}
async connect() {
try {
this.port = await navigator.serial.requestPort();
await this.port.open({
baudRate: this.baudRate,
dataBits: this.dataBits,
stopBits: this.stopBits,
parity: this.parity,
flowControl: this.flowControl
});
this.reader = this.port.readable.getReader();
this.writer = this.port.writable.getWriter();
console.log('Connected to serial port with configuration:', {
baudRate: this.baudRate,
dataBits: this.dataBits,
stopBits: this.stopBits,
parity: this.parity,
flowControl: this.flowControl,
timeout: this.timeout
});
} catch (error) {
console.error('Failed to open serial port:', error);
}
}
async disconnect() {
if (this.reader) {
await this.reader.releaseLock();
}
if (this.writer) {
await this.writer.releaseLock();
}
if (this.port) {
await this.port.close();
console.log('Serial port closed');
}
}
async readHoldingRegisters(slaveId, startAddress, quantity) {
const response = await this.readRegisters(slaveId, 0x03, startAddress, quantity);
return this.parseRegisterValues(response, quantity);
}
async readInputRegisters(slaveId, startAddress, quantity) {
const response = await this.readRegisters(slaveId, 0x04, startAddress, quantity);
return this.parseRegisterValues(response, quantity);
}
async readCoils(slaveId, startAddress, quantity) {
const response = await this.readRegisters(slaveId, 0x01, startAddress, quantity);
return this.parseCoilValues(response, quantity);
}
async readDiscreteInputs(slaveId, startAddress, quantity) {
const response = await this.readRegisters(slaveId, 0x02, startAddress, quantity);
return this.parseCoilValues(response, quantity);
}
async readRegisters(slaveId, functionCode, startAddress, quantity) {
const frameLength = 8; // 8 bytes for reading (slave ID, function code, start address, quantity, CRC)
if (frameLength > 250) {
throw new Error('Read frame length exceeded: frame too long.');
}
const request = this.buildRequest(slaveId, functionCode, startAddress, quantity);
await this.sendRequest(request);
return await this.receiveResponse(slaveId, functionCode, quantity);
}
buildRequest(slaveId, functionCode, address, quantity) {
const request = new Uint8Array(8);
request[0] = slaveId;
request[1] = functionCode;
request[2] = (address >> 8) & 0xFF;
request[3] = address & 0xFF;
request[4] = (quantity >> 8) & 0xFF;
request[5] = quantity & 0xFF;
const crc = this.calculateCRC(request.subarray(0, 6));
request[6] = crc & 0xFF;
request[7] = (crc >> 8) & 0xFF;
return request;
}
calculateCRC(buffer) {
let crc = 0xFFFF;
for (let pos = 0; pos < buffer.length; pos++) {
crc ^= buffer[pos];
for (let i = 8; i !== 0; i--) {
if ((crc & 0x0001) !== 0) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
async sendRequest(request) {
await this.writer.write(request);
//console.log('Request sent:', request);
}
// Receiving and validating response with timeout detection and Modbus Exception Codes processing
async receiveResponse(slaveId, functionCode, quantity) {
// Set the expected length of the response (slaveId, functionCode, byte count, data, CRC)
let expectedLength = 5 + quantity * 2;
let response = new Uint8Array(expectedLength);
let index = 0;
try {
await Promise.race([
(async () => {
while (index < expectedLength) {
const { value, done } = await this.reader.read();
if (done) throw new Error('Device has been lost');
response.set(value, index);
index += value.length;
// If an exception is detected (highest bit of the function code), set the length to 5 bytes (slaveId, functionCode, exceptionCode, CRC)
if (index >= 2 && (response[1] & 0x80)) {
expectedLength = 5; // Override expected length in case of an exception
}
}
})(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout: No response received within the time limit.')), this.timeout)
)
]);
// Modbus Exception Codes processing
if (response[1] & 0x80) { // Check if the highest bit of the function code is set
const exceptionCode = response[2];
throw new Error(`Modbus Exception Code: ${this.getExceptionMessage(exceptionCode)} (Code: ${exceptionCode})`);
}
// Check CRC after receiving the full response
const dataWithoutCRC = response.slice(0, index - 2);
const receivedCRC = (response[index - 1] << 8) | response[index - 2];
const calculatedCRC = this.calculateCRC(dataWithoutCRC);
if (calculatedCRC === receivedCRC) {
//console.log('Received response with valid CRC:', response.slice(0, index));
return response.slice(0, index);
} else {
throw new Error(`CRC Error: Calculated CRC ${calculatedCRC} does not match received CRC ${receivedCRC}.`);
}
} catch (error) {
console.error('Error receiving response:', error.message);
if (error.message.includes('Device has been lost')) {
await this.handleDeviceLost();
}
return { error: error.message };
}
}
async handleDeviceLost() {
console.warn('Attempting to reconnect...');
await this.disconnect();
await this.connect();
}
getExceptionMessage(code) {
const exceptionMessages = {
1: 'Illegal Function',
2: 'Illegal Data Address',
3: 'Illegal Data Value',
4: 'Slave Device Failure',
5: 'Acknowledge',
6: 'Slave Device Busy',
8: 'Memory Parity Error',
10: 'Gateway Path Unavailable',
11: 'Gateway Target Device Failed to Respond'
};
return exceptionMessages[code] || 'Unknown Error';
}
parseRegisterValues(response, quantity) {
const values = [];
for (let i = 0; i < quantity; i++) {
const value = (response[3 + i * 2] << 8) | response[4 + i * 2];
values.push(value);
}
return values;
}
parseCoilValues(response, quantity) {
const values = [];
for (let i = 0; i < quantity; i++) {
const byteIndex = 3 + Math.floor(i / 8);
const bitIndex = i % 8;
const value = (response[byteIndex] & (1 << bitIndex)) !== 0 ? 1 : 0;
values.push(value);
}
return values;
}
}