mirror of
				https://github.com/Pablo2048/modbus-rtu-master.git
				synced 2025-10-31 08:22:43 +01:00 
			
		
		
		
	Compare commits
	
		
			24 Commits
		
	
	
		
			v1.0.0
			...
			developmen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 855e023758 | |||
| 33299110a3 | |||
| f7494799b9 | |||
| e5a665b29f | |||
| 4aff096f7b | |||
| 1ac634fd8d | |||
| f634bf648a | |||
| cd80deb142 | |||
| eaa95d08d0 | |||
| 55236c9356 | |||
| 13a19f930f | |||
| e5bcf00422 | |||
| d83a2db169 | |||
| 29b7f6de2c | |||
| 02b6f692b0 | |||
| 88cb121cb2 | |||
| 5434dcbbc8 | |||
| a7c83f738f | |||
| c7fba48b19 | |||
| c16d2ed08b | |||
| 1350335c75 | |||
| 5f3569d6be | |||
| 4f5fb3daa8 | |||
| 6272dbf38a | 
| @@ -1,9 +1,12 @@ | |||||||
| # Modbus-RTU-Master | # 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. | 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). | 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). | ||||||
|  |  | ||||||
| # Usage in Firefox Browser | # Usage in Firefox Browser | ||||||
|  |  | ||||||
| Unfortunately, Firefox does not natively support the WebSerial API, but luckily, you can use this plugin https://addons.mozilla.org/en-US/firefox/addon/webserial-for-firefox/ to add support. However, currently, uploading via file:// does not work, so you need to run practically any web server locally, for example, in Python using `python -m http.server`. The web application will then be available at http://localhost:8000, and WebSerial will function normally. | Unfortunately, Firefox does not natively support the WebSerial API, but luckily, you can use this plugin https://addons.mozilla.org/en-US/firefox/addon/webserial-for-firefox/ to add support. However, uploading via file:// does not work, because of Firefox policy, so you need to run practically any web server locally, for example, in Python using `python -m http.server`. The web application will then be available at http://localhost:8000, and WebSerial will function normally. | ||||||
|   | |||||||
| @@ -55,12 +55,16 @@ | |||||||
|             <option value="odd">Odd</option> |             <option value="odd">Odd</option> | ||||||
|         </select> |         </select> | ||||||
|         <label for="timeout">Timeout (ms):</label> |         <label for="timeout">Timeout (ms):</label> | ||||||
|         <input type="number" id="timeout" value="150"> |         <input type="number" id="timeout" value="300"> | ||||||
|     </div> |     </div> | ||||||
|     <button id="connect-btn">Připojit k sériovému portu</button> |     <button id="connect-btn">Připojit k sériovému portu</button> | ||||||
|     <div id="register-value">Hodnota registru: <span id="value">N/A</span></div> |     <div id="register-value">Hodnota registru: <span id="value">N/A</span></div> | ||||||
|     <div id="error-message"></div> |     <div id="error-message"></div> | ||||||
|  |     <script> | ||||||
|  |         window.LOG_LEVEL = 'VERBOSE'; | ||||||
|  |         window.ENABLED_MODULES = ['*']; | ||||||
|  |     </script> | ||||||
|  |     <script src="https://pablo2048.github.io/trace.js/src/trace.js"></script> | ||||||
|     <script src="../src/modbus-rtu-master.js"></script> |     <script src="../src/modbus-rtu-master.js"></script> | ||||||
|     <script> |     <script> | ||||||
|         let modbus; |         let modbus; | ||||||
| @@ -92,12 +96,12 @@ | |||||||
|                     valueDisplay.textContent = registerValue; |                     valueDisplay.textContent = registerValue; | ||||||
|                     errorDisplay.textContent = ''; // Vymazat případné staré chyby |                     errorDisplay.textContent = ''; // Vymazat případné staré chyby | ||||||
|                 } else { |                 } else { | ||||||
|                     console.error('Neplatná odpověď:', values); |                     TRACE_ERROR("", 'Neplatná odpověď:', values); | ||||||
|                     valueDisplay.textContent = 'Error'; |                     valueDisplay.textContent = 'Error'; | ||||||
|                     errorDisplay.textContent = 'Invalid response received.'; |                     errorDisplay.textContent = 'Invalid response received.'; | ||||||
|                 } |                 } | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.error('Chyba při čtení registru:', error); |                 TRACE_ERROR("", 'Chyba při čtení registru:', error); | ||||||
|                 valueDisplay.textContent = 'Error'; |                 valueDisplay.textContent = 'Error'; | ||||||
|                 errorDisplay.textContent = error.message || 'Unknown error occurred.'; |                 errorDisplay.textContent = error.message || 'Unknown error occurred.'; | ||||||
|             } |             } | ||||||
| @@ -112,6 +116,7 @@ | |||||||
|                 intervalId = setInterval(updateRegisterValue, 5000); // Každých 5 sekund |                 intervalId = setInterval(updateRegisterValue, 5000); // Každých 5 sekund | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |         TRACE_INFO("", "Starting..."); | ||||||
|     </script> |     </script> | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -9,21 +9,23 @@ class ModbusRTUMaster { | |||||||
|         this.parity = config.parity || 'none'; |         this.parity = config.parity || 'none'; | ||||||
|         this.flowControl = config.flowControl || 'none'; |         this.flowControl = config.flowControl || 'none'; | ||||||
|         this.timeout = config.timeout || 2000; // Default timeout is 2 seconds |         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() { |     async connect() { | ||||||
|         try { |         try { | ||||||
|             this.port = await navigator.serial.requestPort(); |             this.port = await navigator.serial.requestPort(); | ||||||
|             await this.port.open({ |             await this.openPort(); | ||||||
|                 baudRate: this.baudRate, |             TRACE_VERBOSE("mbrtu", 'Connected to serial port with configuration:', { | ||||||
|                 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, |                 baudRate: this.baudRate, | ||||||
|                 dataBits: this.dataBits, |                 dataBits: this.dataBits, | ||||||
|                 stopBits: this.stopBits, |                 stopBits: this.stopBits, | ||||||
| @@ -32,23 +34,73 @@ class ModbusRTUMaster { | |||||||
|                 timeout: this.timeout |                 timeout: this.timeout | ||||||
|             }); |             }); | ||||||
|         } catch (error) { |         } 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() { |     async disconnect() { | ||||||
|         if (this.reader) { |         TRACE_ERROR("mbrtu", "Disconnect."); | ||||||
|             await this.reader.releaseLock(); |         try { | ||||||
|         } |             if (this.reader) { | ||||||
|         if (this.writer) { |                 await this.reader.cancel(); | ||||||
|             await this.writer.releaseLock(); |                 await this.reader.releaseLock(); | ||||||
|         } |                 this.reader = null; | ||||||
|         if (this.port) { |             } | ||||||
|             await this.port.close(); |             if (this.writer) { | ||||||
|             console.log('Serial port closed'); |                 await this.writer.releaseLock(); | ||||||
|  |                 this.writer = null; | ||||||
|  |             } | ||||||
|  |             if (this.port) { | ||||||
|  |                 await this.port.close(); | ||||||
|  |                 this.port = null; | ||||||
|  |                 TRACE_INFO("mbrtu", 'Serial port closed'); | ||||||
|  |             } | ||||||
|  |         } catch (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) { |     async readHoldingRegisters(slaveId, startAddress, quantity) { | ||||||
|         const response = await this.readRegisters(slaveId, 0x03, startAddress, quantity); |         const response = await this.readRegisters(slaveId, 0x03, startAddress, quantity); | ||||||
|         return this.parseRegisterValues(response, quantity); |         return this.parseRegisterValues(response, quantity); | ||||||
| @@ -76,6 +128,7 @@ class ModbusRTUMaster { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         const request = this.buildRequest(slaveId, functionCode, startAddress, quantity); |         const request = this.buildRequest(slaveId, functionCode, startAddress, quantity); | ||||||
|  |         TRACE_VERBOSE("mbrtu", "Send request"); | ||||||
|         await this.sendRequest(request); |         await this.sendRequest(request); | ||||||
|         return await this.receiveResponse(slaveId, functionCode, quantity); |         return await this.receiveResponse(slaveId, functionCode, quantity); | ||||||
|     } |     } | ||||||
| @@ -112,12 +165,10 @@ class ModbusRTUMaster { | |||||||
|  |  | ||||||
|     async sendRequest(request) { |     async sendRequest(request) { | ||||||
|         await this.writer.write(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) { |     async receiveResponse(slaveId, functionCode, quantity) { | ||||||
|         // Set the expected length of the response (slaveId, functionCode, byte count, data, CRC) |  | ||||||
|         let expectedLength = 5 + quantity * 2; |         let expectedLength = 5 + quantity * 2; | ||||||
|         let response = new Uint8Array(expectedLength); |         let response = new Uint8Array(expectedLength); | ||||||
|         let index = 0; |         let index = 0; | ||||||
| @@ -131,9 +182,8 @@ class ModbusRTUMaster { | |||||||
|                         response.set(value, index); |                         response.set(value, index); | ||||||
|                         index += value.length; |                         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)) { |                         if (index >= 2 && (response[1] & 0x80)) { | ||||||
|                             expectedLength = 5; // Override expected length in case of an exception |                             expectedLength = 5; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 })(), |                 })(), | ||||||
| @@ -142,38 +192,36 @@ class ModbusRTUMaster { | |||||||
|                 ) |                 ) | ||||||
|             ]); |             ]); | ||||||
|  |  | ||||||
|             // Modbus Exception Codes processing |             if (response[1] & 0x80) { | ||||||
|             if (response[1] & 0x80) { // Check if the highest bit of the function code is set |  | ||||||
|                 const exceptionCode = response[2]; |                 const exceptionCode = response[2]; | ||||||
|                 throw new Error(`Modbus Exception Code: ${this.getExceptionMessage(exceptionCode)} (Code: ${exceptionCode})`); |                 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 dataWithoutCRC = response.slice(0, index - 2); | ||||||
|             const receivedCRC = (response[index - 1] << 8) | response[index - 2]; |             const receivedCRC = (response[index - 1] << 8) | response[index - 2]; | ||||||
|             const calculatedCRC = this.calculateCRC(dataWithoutCRC); |             const calculatedCRC = this.calculateCRC(dataWithoutCRC); | ||||||
|  |  | ||||||
|             if (calculatedCRC === receivedCRC) { |             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); |                 return response.slice(0, index); | ||||||
|             } else { |             } else { | ||||||
|                 throw new Error(`CRC Error: Calculated CRC ${calculatedCRC} does not match received CRC ${receivedCRC}.`); |                 throw new Error(`CRC Error: Calculated CRC ${calculatedCRC} does not match received CRC ${receivedCRC}.`); | ||||||
|             } |             } | ||||||
|         } catch (error) { |         } 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(); | ||||||
|  |                 this.reader = null; | ||||||
|  |                 this.reader = this.port.readable.getReader(); | ||||||
|  |             } | ||||||
|             if (error.message.includes('Device has been lost')) { |             if (error.message.includes('Device has been lost')) { | ||||||
|                 await this.handleDeviceLost(); |                 await this.handleDeviceLost(); | ||||||
|             } |             } | ||||||
|             return { error: error.message }; |             throw error; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async handleDeviceLost() { |  | ||||||
|         console.warn('Attempting to reconnect...'); |  | ||||||
|         await this.disconnect(); |  | ||||||
|         await this.connect(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     getExceptionMessage(code) { |     getExceptionMessage(code) { | ||||||
|         const exceptionMessages = { |         const exceptionMessages = { | ||||||
|             1: 'Illegal Function', |             1: 'Illegal Function', | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								update_dev.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								update_dev.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # Stáhni všechny větve z origin | ||||||
|  | git fetch origin | ||||||
|  |  | ||||||
|  | # Přepni se na main a stáhni nejnovější změny | ||||||
|  | git checkout main | ||||||
|  | git pull origin main | ||||||
|  |  | ||||||
|  | # Zkontroluj, jestli existuje větev development lokálně, pokud ne, vytvoř ji | ||||||
|  | if git show-ref --quiet refs/heads/development; then | ||||||
|  |   git checkout development | ||||||
|  | else | ||||||
|  |   git checkout -b development origin/development | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Stáhni nejnovější změny z development větve | ||||||
|  | git pull origin development | ||||||
|  |  | ||||||
|  | # Proveď merge z main do development s povolením nesouvisejících historií | ||||||
|  | git merge main --allow-unrelated-histories | ||||||
|  |  | ||||||
|  | # Pushni změny do vzdálené development větve | ||||||
|  | git push origin development | ||||||
		Reference in New Issue
	
	Block a user