diff --git a/README.md b/README.md index ae1f0d5..9a2a1d3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Modbus-RTU-Master +> [!NOTE] +> The library now uses my https://github.com/Pablo2048/trace.js javascript library for debugging output. + Implementation of a Modbus RTU master device in JavaScript, usable in web applications in Chrome and Firefox browsers. The module was primarily written for web applications used by my beloved wife and serves for commissioning and testing devices at our workplace. It supports reading and writing from all Modbus areas, but only input registers and holding registers are tested/actively used. The repository also includes a simple usage example that allows setting communication parameters and periodically reads one register every 5 seconds, displaying it on the web page (please excuse the Czech comments in the source code). diff --git a/example/index.html b/example/index.html index 13d5384..b46d65a 100644 --- a/example/index.html +++ b/example/index.html @@ -55,12 +55,16 @@ - +
Hodnota registru: N/A
- + + diff --git a/src/modbus-rtu-master.js b/src/modbus-rtu-master.js index 447cc4f..e43d849 100644 --- a/src/modbus-rtu-master.js +++ b/src/modbus-rtu-master.js @@ -9,21 +9,23 @@ class ModbusRTUMaster { this.parity = config.parity || 'none'; this.flowControl = config.flowControl || 'none'; this.timeout = config.timeout || 2000; // Default timeout is 2 seconds + + navigator.serial.addEventListener('disconnect', (event) => { + TRACE_WARNING("mbrtu", 'Port disconnected, attempting to reconnect...'); + this.handleDeviceLost(); + }); + + navigator.serial.addEventListener('connect', (event) => { + TRACE_INFO("mbrtu", 'Device connected:', event); + this.reconnect(); + }); } 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:', { + await this.openPort(); + TRACE_VERBOSE("mbrtu", 'Connected to serial port with configuration:', { baudRate: this.baudRate, dataBits: this.dataBits, stopBits: this.stopBits, @@ -32,12 +34,24 @@ class ModbusRTUMaster { timeout: this.timeout }); } catch (error) { - console.error('Failed to open serial port:', error); + TRACE_ERROR("mbrtu", 'Failed to open serial port:', error); } } + async openPort() { + 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(); + } + async disconnect() { - console.log('Disconnect requested'); + TRACE_ERROR("mbrtu", "Disconnect."); try { if (this.reader) { await this.reader.cancel(); @@ -51,13 +65,42 @@ class ModbusRTUMaster { if (this.port) { await this.port.close(); this.port = null; - console.log('Serial port closed'); + TRACE_INFO("mbrtu", 'Serial port closed'); } } catch (error) { - console.error('Error during disconnect:', error); + TRACE_ERROR("mbrtu", 'Error during disconnect:', error); } } + async handleDeviceLost() { + TRACE_WARNING("mbrtu", 'Attempting to reconnect...'); + await this.disconnect(); + this.reconnect(); + } + + reconnect() { + setTimeout(async () => { + try { + const ports = await navigator.serial.getPorts(); + if (ports.length > 0) { + this.port = ports[0]; + if (!this.port.readable && !this.port.writable) { + await this.openPort(); + TRACE_INFO("mbrtu", 'Reconnected to serial port.'); + } else { + TRACE_WARNING("mbrtu", 'Port is already open. Skipping open operation.'); + } + } else { + TRACE_WARNING("mbrtu", 'Port not found, retrying...'); + this.reconnect(); + } + } catch (error) { + TRACE_ERROR("mbrtu", 'Reconnect failed:', error); + this.reconnect(); + } + }, 1000); // Retry every 1 second + } + async readHoldingRegisters(slaveId, startAddress, quantity) { const response = await this.readRegisters(slaveId, 0x03, startAddress, quantity); return this.parseRegisterValues(response, quantity); @@ -85,6 +128,7 @@ class ModbusRTUMaster { } const request = this.buildRequest(slaveId, functionCode, startAddress, quantity); + TRACE_VERBOSE("mbrtu", "Send request"); await this.sendRequest(request); return await this.receiveResponse(slaveId, functionCode, quantity); } @@ -121,12 +165,10 @@ class ModbusRTUMaster { async sendRequest(request) { await this.writer.write(request); - //console.log('Request sent:', request); + TRACE_VERBOSE("mbrtu", '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; @@ -140,9 +182,8 @@ class ModbusRTUMaster { 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 + expectedLength = 5; } } })(), @@ -151,25 +192,23 @@ class ModbusRTUMaster { ) ]); - // Modbus Exception Codes processing - if (response[1] & 0x80) { // Check if the highest bit of the function code is set + if (response[1] & 0x80) { 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)); + TRACE_VERBOSE("mbrtu", '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); + TRACE_ERROR("mbrtu", 'Error receiving response:', error.message); if (this.reader) { await this.reader.cancel(); await this.reader.releaseLock(); @@ -179,16 +218,10 @@ class ModbusRTUMaster { if (error.message.includes('Device has been lost')) { await this.handleDeviceLost(); } - return { error: error.message }; // TODO: or throw error; ? + throw error; } } - async handleDeviceLost() { - console.warn('Attempting to reconnect...'); - await this.disconnect(); - await this.connect(); - } - getExceptionMessage(code) { const exceptionMessages = { 1: 'Illegal Function',