Skip to content

Commit 7e80571

Browse files
authored
Merge pull request #30 from budulinek/dev
Dev
2 parents da71369 + df50206 commit 7e80571

File tree

7 files changed

+126
-81
lines changed

7 files changed

+126
-81
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ In the next step, add individual Modbus devices. Adding and configuring Modbus d
224224
* download **Device Template** from the **[Loxone Library](https://library.loxone.com/)** (there are already hundreds of templates for various Modbus devices)
225225
* manually add your device following this **[official tutorial](https://www.loxone.com/enen/kb/communication-with-modbus/)**.
226226

227-
Please note that the implementation of Modbus RTU (= Loxone Modbus Extension) and Modbus TCP (= Arduino Modbus gateway connected as "Modbus Server") in Loxone is flawed:
228-
* Miniserver can not poll your Modbus sensors faster than 5 seconds. This is a deliberate restriction imposed by Loxone.
227+
Please note that the implementation of Modbus RTU (= Loxone Modbus Extension) and Modbus TCP (= Arduino Modbus gateway connected as "Modbus Server") in Loxone has some restrictions:
228+
* ~~Miniserver can not poll your Modbus sensors faster than 5 seconds. This is a deliberate restriction imposed by Loxone.~~ **Fixed in Loxone Config 14.4.9.25**. Minimum polling-cycle reduced to 1s (except Air-Devices), up to 2 Sensors per Modbus-Server or Extension allow a minimum time of 0.1s.
229229
* Miniserver can not poll multiple Modbus registers at once. If you have multiple sensors on the device, Loxone will send a separate requests for each of them even if you have identical poll intervals for these sensors. This is a design flaw by Loxone.
230230

231231
**Modbus UDP**. If you want to avoid the above mentioned limitations, you can use Modbus UDP as a communication protocol between Loxone and this Arduino Modbus gateway. See [Loxone_ModbusUDP.md](Loxone_ModbusUDP.md) on how to implement Modbus UDP in Loxone with **Virtual UDP output** and **Virtual UDP input**.

arduino-modbus-rtu-tcp-gateway/01-interfaces.ino

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,29 @@ void startEthernet() {
114114

115115
void (*resetFunc)(void) = 0; //declare reset function at address 0
116116

117+
void checkEthernet() {
118+
static byte attempts = 0;
119+
IPAddress tempIP = Ethernet.localIP();
120+
if (tempIP[0] == 0) {
121+
attempts++;
122+
if (attempts >= 3) {
123+
resetFunc();
124+
}
125+
} else {
126+
attempts = 0;
127+
}
128+
checkEthTimer.sleep(CHECK_ETH_INTERVAL);
129+
}
130+
117131
#ifdef ENABLE_DHCP
118132
void maintainDhcp() {
119133
if (data.config.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
120-
byte maintainResult = Ethernet.maintain();
121-
if (maintainResult == 1 || maintainResult == 3) { // renew failed or rebind failed
122-
dhcpSuccess = false;
123-
startEthernet(); // another DHCP request, fallback to static IP
124-
}
134+
Ethernet.maintain();
125135
}
126136
}
127137
#endif /* ENABLE_DHCP */
128138

129-
#ifdef ENABLE_EXTRA_DIAG
139+
#ifdef ENABLE_EXTENDED_WEBUI
130140
void maintainUptime() {
131141
uint32_t milliseconds = millis();
132142
if (last_milliseconds > milliseconds) {
@@ -140,7 +150,7 @@ void maintainUptime() {
140150
//We add the "remaining_seconds", so that we can continue measuring the time passed from the last boot of the device.
141151
seconds = (milliseconds / 1000) + remaining_seconds;
142152
}
143-
#endif /* ENABLE_EXTRA_DIAG */
153+
#endif /* ENABLE_EXTENDED_WEBUI */
144154

145155
bool rollover() {
146156
// synchronize roll-over of run time, data counters and modbus stats to zero, at 0xFFFFFF00
@@ -150,7 +160,7 @@ bool rollover() {
150160
return true;
151161
}
152162
}
153-
#ifdef ENABLE_EXTRA_DIAG
163+
#ifdef ENABLE_EXTENDED_WEBUI
154164
if (seconds > ROLLOVER) {
155165
return true;
156166
}
@@ -159,18 +169,18 @@ bool rollover() {
159169
return true;
160170
}
161171
}
162-
#endif /* ENABLE_EXTRA_DIAG */
172+
#endif /* ENABLE_EXTENDED_WEBUI */
163173
return false;
164174
}
165175

166176
// resets counters to 0: data.errorCnt, data.rtuCnt, data.ethCnt
167177
void resetStats() {
168178
memset(data.errorCnt, 0, sizeof(data.errorCnt));
169-
#ifdef ENABLE_EXTRA_DIAG
179+
#ifdef ENABLE_EXTENDED_WEBUI
170180
memset(data.rtuCnt, 0, sizeof(data.rtuCnt));
171181
memset(data.ethCnt, 0, sizeof(data.ethCnt));
172182
remaining_seconds = -(millis() / 1000);
173-
#endif /* ENABLE_EXTRA_DIAG */
183+
#endif /* ENABLE_EXTENDED_WEBUI */
174184
}
175185

176186
// generate new MAC (bytes 0, 1 and 2 are static, bytes 3, 4 and 5 are generated randomly)
@@ -208,7 +218,7 @@ void manageSockets() {
208218
byte webListening = MAX_SOCK_NUM;
209219
byte dataAvailable = MAX_SOCK_NUM;
210220
byte socketsAvailable = 0;
211-
// SPI.beginTransaction(SPI_ETHERNET_SETTINGS); // begin SPI transaction
221+
SPI.beginTransaction(SPI_ETHERNET_SETTINGS); // begin SPI transaction
212222
// look at all the hardware sockets, record and take action based on current states
213223
for (byte s = 0; s < maxSockNum; s++) { // for each hardware socket ...
214224
byte status = W5100.readSnSR(s); // get socket status...
@@ -294,8 +304,8 @@ void manageSockets() {
294304
disconSocket(oldest);
295305
}
296306

297-
// SPI.endTransaction(); // Serves to o release the bus for other devices to access it. Since the ethernet chip is the only device
298-
// we do not need SPI.beginTransaction(SPI_ETHERNET_SETTINGS) or SPI.endTransaction()
307+
SPI.endTransaction(); // Serves to o release the bus for other devices to access it. Since the ethernet chip is the only device
308+
// we do not need SPI.beginTransaction(SPI_ETHERNET_SETTINGS) or SPI.endTransaction() ??
299309
}
300310

301311
void disconSocket(byte s) {

arduino-modbus-rtu-tcp-gateway/02-modbus-tcp.ino

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ uint16_t crc;
4242
void recvUdp() {
4343
uint16_t msgLength = Udp.parsePacket();
4444
if (msgLength) {
45-
#ifdef ENABLE_EXTRA_DIAG
45+
#ifdef ENABLE_EXTENDED_WEBUI
4646
data.ethCnt[DATA_RX] += msgLength;
47-
#endif /* ENABLE_EXTRA_DIAG */
47+
#endif /* ENABLE_EXTENDED_WEBUI */
4848
byte inBuffer[MODBUS_SIZE + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame
4949
// Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address)..... no CRC
5050
// Modbus RTU frame: [0] address.....[n-1][n] CRC
@@ -72,19 +72,19 @@ void recvUdp() {
7272
Udp.write(highByte(crc));
7373
}
7474
Udp.endPacket();
75-
#ifdef ENABLE_EXTRA_DIAG
75+
#ifdef ENABLE_EXTENDED_WEBUI
7676
data.ethCnt[DATA_TX] += 5;
7777
if (!data.config.enableRtuOverTcp) data.ethCnt[DATA_TX] += 4;
78-
#endif /* ENABLE_EXTRA_DIAG */
78+
#endif /* ENABLE_EXTENDED_WEBUI */
7979
}
8080
}
8181
}
8282

8383
void recvTcp(EthernetClient &client) {
8484
uint16_t msgLength = client.available();
85-
#ifdef ENABLE_EXTRA_DIAG
85+
#ifdef ENABLE_EXTENDED_WEBUI
8686
data.ethCnt[DATA_RX] += msgLength;
87-
#endif /* ENABLE_EXTRA_DIAG */
87+
#endif /* ENABLE_EXTENDED_WEBUI */
8888
byte inBuffer[MODBUS_SIZE + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame
8989
// Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address).....
9090
// Modbus RTU frame: [0] address.....
@@ -114,10 +114,10 @@ void recvTcp(EthernetClient &client) {
114114
outBuffer[i++] = highByte(crc);
115115
}
116116
client.write(outBuffer, i);
117-
#ifdef ENABLE_EXTRA_DIAG
117+
#ifdef ENABLE_EXTENDED_WEBUI
118118
data.ethCnt[DATA_TX] += 5;
119119
if (!data.config.enableRtuOverTcp) data.ethCnt[DATA_TX] += 4;
120-
#endif /* ENABLE_EXTRA_DIAG */
120+
#endif /* ENABLE_EXTENDED_WEBUI */
121121
}
122122
}
123123

arduino-modbus-rtu-tcp-gateway/03-modbus-rtu.ino

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ void sendSerial() {
7070
break;
7171
case 2: // DELAY:
7272
{
73-
#ifdef ENABLE_EXTRA_DIAG
73+
#ifdef ENABLE_EXTENDED_WEBUI
7474
data.rtuCnt[DATA_TX] += myHeader.msgLen;
7575
data.rtuCnt[DATA_TX] += 2;
7676
#endif
@@ -157,9 +157,9 @@ void recvSerial() {
157157
} else {
158158
data.errorCnt[ERROR_RTU]++;
159159
}
160-
#ifdef ENABLE_EXTRA_DIAG
160+
#ifdef ENABLE_EXTENDED_WEBUI
161161
data.rtuCnt[DATA_RX] += rxNdx;
162-
#endif /* ENABLE_EXTRA_DIAG */
162+
#endif /* ENABLE_EXTENDED_WEBUI */
163163
rxNdx = 0;
164164
}
165165
}
@@ -181,10 +181,10 @@ void sendResponse(const byte MBAP[], const byte PDU[], const uint16_t pduLength)
181181
Udp.write(PDU, pduLength - 2); //send without CRC
182182
}
183183
Udp.endPacket();
184-
#ifdef ENABLE_EXTRA_DIAG
184+
#ifdef ENABLE_EXTENDED_WEBUI
185185
data.ethCnt[DATA_TX] += pduLength;
186186
if (!data.config.enableRtuOverTcp) data.ethCnt[DATA_TX] += 4;
187-
#endif /* ENABLE_EXTRA_DIAG */
187+
#endif /* ENABLE_EXTENDED_WEBUI */
188188
} else if (myHeader.requestType & TCP_REQUEST) {
189189
byte sock = myHeader.requestType & TCP_REQUEST_MASK;
190190
EthernetClient client = EthernetClient(sock);
@@ -194,10 +194,10 @@ void sendResponse(const byte MBAP[], const byte PDU[], const uint16_t pduLength)
194194
client.write(MBAP, 6);
195195
client.write(PDU, pduLength - 2); //send without CRC
196196
}
197-
#ifdef ENABLE_EXTRA_DIAG
197+
#ifdef ENABLE_EXTENDED_WEBUI
198198
data.ethCnt[DATA_TX] += pduLength;
199199
if (!data.config.enableRtuOverTcp) data.ethCnt[DATA_TX] += 4;
200-
#endif /* ENABLE_EXTRA_DIAG */
200+
#endif /* ENABLE_EXTENDED_WEBUI */
201201
} // TODO TCP Connection Error
202202
} // else SCAN_REQUEST (no data.ethCnt[DATA_TX], but yes delete request)
203203
deleteRequest();

arduino-modbus-rtu-tcp-gateway/05-pages.ino

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ void contentInfo(ChunkedPrint &chunked) {
260260
// Modbus Status
261261
void contentStatus(ChunkedPrint &chunked) {
262262

263-
#ifdef ENABLE_EXTRA_DIAG
263+
#ifdef ENABLE_EXTENDED_WEBUI
264264
tagLabelDiv(chunked, F("Run Time"));
265265
tagSpan(chunked, JSON_TIME);
266266
tagDivClose(chunked);
@@ -270,7 +270,7 @@ void contentStatus(ChunkedPrint &chunked) {
270270
tagLabelDiv(chunked, F("Ethernet Data"));
271271
tagSpan(chunked, JSON_ETH_DATA);
272272
tagDivClose(chunked);
273-
#endif /* ENABLE_EXTRA_DIAG */
273+
#endif /* ENABLE_EXTENDED_WEBUI */
274274

275275
tagLabelDiv(chunked, F("Modbus RTU Request"));
276276
for (byte i = 0; i <= POST_REQ_LAST - POST_REQ; i++) {
@@ -650,7 +650,7 @@ void stringStats(ChunkedPrint &chunked, const byte stat) {
650650

651651
void jsonVal(ChunkedPrint &chunked, const byte JSONKEY) {
652652
switch (JSONKEY) {
653-
#ifdef ENABLE_EXTRA_DIAG
653+
#ifdef ENABLE_EXTENDED_WEBUI
654654
case JSON_TIME:
655655
chunked.print(seconds / (3600UL * 24L));
656656
chunked.print(F(" days, "));
@@ -686,7 +686,7 @@ void jsonVal(ChunkedPrint &chunked, const byte JSONKEY) {
686686
}
687687
}
688688
break;
689-
#endif /* ENABLE_EXTRA_DIAG */
689+
#endif /* ENABLE_EXTENDED_WEBUI */
690690
case JSON_RESPONSE:
691691
{
692692
for (byte i = 0; i < MAX_RESPONSE_LEN; i++) {
Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,53 @@
11
/* Advanced settings, extra functions and default config for Modbus RTU ⇒ Modbus TCP/UDP Gateway
22
*/
33

4+
/****** FUNCTIONALITY ******/
5+
6+
// #define ENABLE_EXTENDED_WEBUI // Enable extended Web UI (additional items and settings), consumes FLASH memory
7+
// uncomment ENABLE_EXTENDED_WEBUI if you have a board with large FLASH memory (Arduino Mega)
8+
9+
// #define ENABLE_DHCP // Enable DHCP (Auto IP settings), consumes a lot of FLASH memory
10+
11+
12+
/****** DEFAULT CONFIGURATION ******/
13+
/*
14+
Arduino loads user settings stored in EEPROM, even if you flash new program to it.
15+
16+
Arduino loads factory defaults if:
17+
1) User clicks "Load default settings" in WebUI (factory reset configuration, keeps MAC)
18+
2) VERSION_MAJOR changes (factory reset configuration AND generates new MAC)
19+
*/
20+
21+
/****** IP Settings ******/
22+
const bool DEFAULT_AUTO_IP = false; // Default Auto IP setting (only used if ENABLE_DHCP)
23+
#define DEFAULT_STATIC_IP \
24+
{ 192, 168, 1, 254 } // Default Static IP
25+
#define DEFAULT_SUBMASK \
26+
{ 255, 255, 255, 0 } // Default Submask
27+
#define DEFAULT_GATEWAY \
28+
{ 192, 168, 1, 1 } // Default Gateway
29+
#define DEFAULT_DNS \
30+
{ 192, 168, 1, 1 } // Default DNS Server (only used if ENABLE_DHCP)
31+
32+
/****** TCP/UDP Settings ******/
33+
const uint16_t DEFAULT_TCP_PORT = 502; // Default Modbus TCP Port
34+
const uint16_t DEFAULT_UDP_PORT = 502; // Default Modbus UDP Port
35+
const uint16_t DEFAULT_WEB_PORT = 80; // Default WebUI Port
36+
const bool DEFAULT_RTU_OVER_TCP = false; // Default Modbus Mode (Modbus TCP or Modbus RTU over TCP)
37+
const uint16_t DEFAULT_TCP_TIMEOUT = 600; // Default Modbus TCP Idle Timeout
38+
39+
/****** RTU Settings ******/
40+
const uint16_t DEFAULT_BAUD_RATE = 96; // Default Baud Rate / 100
41+
const byte DEFAULT_SERIAL_CONFIG = SERIAL_8E1; // Default Data Bits, Parity, Stop bits. Modbus default is 8E1, another frequently used option is 8N2
42+
// for all valid options see https://www.arduino.cc/reference/en/language/functions/communication/serial/begin/
43+
const byte DEFAULT_FRAME_DELAY = 150; // Default Inter-frame Delay
44+
const uint16_t DEFAULT_RESPONSE_TIMEPOUT = 500; // Default Response Timeout
45+
const byte DEFAULT_ATTEMPTS = 3; // Default Attempts
46+
447
/****** ADVANCED SETTINGS ******/
548

649
#define mySerial Serial // define serial port for RS485 interface, for Arduino Mega choose from Serial1, Serial2 or Serial3
7-
// List of baud rates (divided by 100) available in WebUI. Feel free to add your custom baud rate (anything between 3 and 2500)
50+
// List of baud rates (divided by 100) available in WebUI. Feel free to add your custom baud rate (anything between 3 and 2500)
851
const uint16_t BAUD_RATES[] = { 3, 6, 9, 12, 24, 48, 96, 192, 384, 576, 1152 };
952
#define RS485_CONTROL_PIN 6 // Arduino Pin for RS485 Direction control, disable if you have module with hardware flow control
1053
const byte MAX_QUEUE_REQUESTS = 10; // max number of TCP or UDP requests stored in a queue
@@ -20,6 +63,7 @@ const uint16_t SCAN_TIMEOUT = 200; // Timeout (ms) for Modbus scan request
2063

2164
const byte MAC_START[3] = { 0x90, 0xA2, 0xDA }; // MAC range for Gheo SA
2265
const byte ETH_RESET_PIN = 7; // Ethernet shield reset pin (deals with power on reset issue on low quality ethernet shields)
66+
const uint16_t CHECK_ETH_INTERVAL = 2000; // Interval (ms) to check SPI connection with ethernet shield
2367
const uint16_t ETH_RESET_DELAY = 500; // Delay (ms) during Ethernet start, wait for Ethernet shield to start (reset issue on low quality ethernet shields)
2468
const uint16_t WEB_IDLE_TIMEOUT = 400; // Time (ms) from last client data after which webserver TCP socket could be disconnected, non-blocking.
2569
const uint16_t TCP_DISCON_TIMEOUT = 500; // Timeout (ms) for client DISCON socket command, non-blocking alternative to https://www.arduino.cc/reference/en/libraries/ethernet/client.setconnectiontimeout/
@@ -28,38 +72,4 @@ const byte TCP_RETRANSMISSION_COUNT = 3; // Number of transmission attem
2872
const uint16_t FETCH_INTERVAL = 2000; // Fetch API interval (ms) for the Modbus Status webpage to renew data from JSON served by Arduino
2973

3074
const byte DATA_START = 96; // Start address where config and counters are saved in EEPROM
31-
const byte EEPROM_INTERVAL = 6; // Interval (hours) for saving Modbus statistics to EEPROM (in order to minimize writes to EEPROM)
32-
33-
/****** EXTRA FUNCTIONS ******/
34-
35-
// these do not fit into the limited flash memory of Arduino Uno/Nano, uncomment if you have a board with more memory
36-
// #define ENABLE_DHCP // Enable DHCP (Auto IP settings)
37-
// #define ENABLE_EXTRA_DIAG // Enable Ethernet and Serial byte counter.
38-
39-
/****** DEFAULT FACTORY SETTINGS ******/
40-
41-
/*
42-
Please note that after boot, Arduino loads user settings stored in EEPROM, even if you flash new program to it!
43-
Arduino loads factory defaults if:
44-
1) User clicks "Load default settings" in WebUI (factory reset configuration, keeps MAC)
45-
2) VERSION_MAJOR changes (factory reset configuration AND generates new MAC)
46-
47-
You can change default factory settings bellow, but do not delete (comment out) individual lines!
48-
*/
49-
const config_t DEFAULT_CONFIG = {
50-
{ 192, 168, 1, 254 }, // Static IP
51-
{ 255, 255, 255, 0 }, // Submask
52-
{ 192, 168, 1, 1 }, // Gateway
53-
{ 192, 168, 1, 1 }, // Dns (only used if ENABLE_DHCP)
54-
false, // enableDhcp (only used if ENABLE_DHCP)
55-
502, // Modbus TCP Port
56-
502, // Modbus UDP Port
57-
80, // WebUI Port
58-
false, // Modbus Mode (enableRTU over TCP)
59-
600, // Modbus TCP Idle Timeout
60-
96, // Baud Rate / 100
61-
SERIAL_8E1, // Serial Config (Data Bits, Parity, Stop bits), Modbus RTU default is 8E1, another frequently used option is 8N2
62-
150, // Inter-frame Delay (byte)
63-
500, // Response Timeout
64-
3, // Attempts (byte)
65-
};
75+
const byte EEPROM_INTERVAL = 6; // Interval (hours) for saving Modbus statistics to EEPROM (in order to minimize writes to EEPROM)

0 commit comments

Comments
 (0)