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 | ||||
|  | ||||
| > [!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). | ||||
|  | ||||
| # 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> | ||||
|         </select> | ||||
|         <label for="timeout">Timeout (ms):</label> | ||||
|         <input type="number" id="timeout" value="150"> | ||||
|         <input type="number" id="timeout" value="300"> | ||||
|     </div> | ||||
|     <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="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> | ||||
|         let modbus; | ||||
| @@ -92,12 +96,12 @@ | ||||
|                     valueDisplay.textContent = registerValue; | ||||
|                     errorDisplay.textContent = ''; // Vymazat případné staré chyby | ||||
|                 } else { | ||||
|                     console.error('Neplatná odpověď:', values); | ||||
|                     TRACE_ERROR("", 'Neplatná odpověď:', values); | ||||
|                     valueDisplay.textContent = 'Error'; | ||||
|                     errorDisplay.textContent = 'Invalid response received.'; | ||||
|                 } | ||||
|             } catch (error) { | ||||
|                 console.error('Chyba při čtení registru:', error); | ||||
|                 TRACE_ERROR("", 'Chyba při čtení registru:', error); | ||||
|                 valueDisplay.textContent = 'Error'; | ||||
|                 errorDisplay.textContent = error.message || 'Unknown error occurred.'; | ||||
|             } | ||||
| @@ -112,6 +116,7 @@ | ||||
|                 intervalId = setInterval(updateRegisterValue, 5000); // Každých 5 sekund | ||||
|             } | ||||
|         }); | ||||
|         TRACE_INFO("", "Starting..."); | ||||
|     </script> | ||||
| </body> | ||||
| </html> | ||||
|   | ||||
| @@ -9,11 +9,36 @@ 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.openPort(); | ||||
|             TRACE_VERBOSE("mbrtu", '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) { | ||||
|             TRACE_ERROR("mbrtu", 'Failed to open serial port:', error); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async openPort() { | ||||
|         await this.port.open({ | ||||
|             baudRate: this.baudRate, | ||||
|             dataBits: this.dataBits, | ||||
| @@ -23,30 +48,57 @@ class ModbusRTUMaster { | ||||
|         }); | ||||
|         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() { | ||||
|         TRACE_ERROR("mbrtu", "Disconnect."); | ||||
|         try { | ||||
|             if (this.reader) { | ||||
|                 await this.reader.cancel(); | ||||
|                 await this.reader.releaseLock(); | ||||
|                 this.reader = null; | ||||
|             } | ||||
|             if (this.writer) { | ||||
|                 await this.writer.releaseLock(); | ||||
|                 this.writer = null; | ||||
|             } | ||||
|             if (this.port) { | ||||
|                 await this.port.close(); | ||||
|             console.log('Serial port closed'); | ||||
|                 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) { | ||||
| @@ -76,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); | ||||
|     } | ||||
| @@ -112,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; | ||||
| @@ -131,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; | ||||
|                         } | ||||
|                     } | ||||
|                 })(), | ||||
| @@ -142,38 +192,36 @@ 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(); | ||||
|                 this.reader = null; | ||||
|                 this.reader = this.port.readable.getReader(); | ||||
|             } | ||||
|             if (error.message.includes('Device has been lost')) { | ||||
|                 await this.handleDeviceLost(); | ||||
|             } | ||||
|             return { error: error.message }; | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async handleDeviceLost() { | ||||
|         console.warn('Attempting to reconnect...'); | ||||
|         await this.disconnect(); | ||||
|         await this.connect(); | ||||
|     } | ||||
|  | ||||
|     getExceptionMessage(code) { | ||||
|         const exceptionMessages = { | ||||
|             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