Skip to content

Commit ac49705

Browse files
committed
Improve POST parameters processing
Use enum to ensure unique identification of each HTTP POST parameter key and consistence across functions. Each web page is also identified using enum.
1 parent ca242be commit ac49705

File tree

3 files changed

+310
-257
lines changed

3 files changed

+310
-257
lines changed

arduino-modbus-rtu-tcp-gateway/04-webserver.ino

Lines changed: 164 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,67 @@
1616
1717
***************************************************************** */
1818

19-
20-
const byte pagesCnt = 5; // number of consecutive pages ("1.htm", "2.htm"....) served by the server, maximum is 9
2119
const byte webInBufferSize = 128; // size of web server read buffer (reads a complete line), 128 bytes necessary for POST data
2220
const byte smallbuffersize = 32; // a smaller buffer for uri
2321

2422
// Actions that need to be taken after saving configuration.
25-
// Order of these actions must correspond to POST param values of buttons on the "Tools" web page (e.g. "value=1" for Restore factory defaults, etc.)
23+
// These actions are also used by buttons on the Tools page.
2624
enum action_type : byte
2725
{
28-
NONE, FACTORY, MAC, REBOOT, ETH_SOFT, SERIAL_SOFT, SCAN, WEB
26+
NONE,
27+
FACTORY, // Restore factory settings (but keep MAC address)
28+
MAC, // Generate new random MAC
29+
REBOOT, // Reboot the microcontroller
30+
ETH_SOFT, // Ethernet software reset
31+
SERIAL_SOFT, // Serial software reset
32+
SCAN, // Initialize RS485 scan
33+
WEB // Restart webserver
2934
};
3035
enum action_type action;
3136

37+
// Pages served by the webserver. Order of elements defines the order in the left menu of the web UI.
38+
// URL of the page (*.htm) contains number corresponding to its position in this array.
39+
// The following enum array can have a maximum of 10 elements (incl. PAGE_NONE and PAGE_WAIT)
40+
enum page : byte
41+
{
42+
PAGE_NONE, // reserved for NULL
43+
PAGE_STATUS,
44+
PAGE_IP,
45+
PAGE_TCP,
46+
PAGE_RTU,
47+
PAGE_TOOLS,
48+
PAGE_WAIT // page with "Reloading. Please wait..." message. Must be the last element within this enum!!
49+
};
50+
51+
// Keys for POST parameters, used in web forms and processed by processPost() function.
52+
// Using enum ensures unique identification of each POST parameter key and consistence across functions.
53+
// In HTML code, each element will apear as number corresponding to its position in this array.
54+
enum post_key : byte
55+
{
56+
POST_NONE, // reserved for NULL
57+
POST_DHCP, // enable DHCP
58+
POST_IP, POST_IP_1, POST_IP_2, POST_IP_3, // IP address || Each part of an IP address has its own POST parameter. ||
59+
POST_SUBNET, POST_SUBNET_1, POST_SUBNET_2, POST_SUBNET_3, // subnet || Because HTML code for IP, subnet, gateway and DNS ||
60+
POST_GATEWAY, POST_GATEWAY_1, POST_GATEWAY_2, POST_GATEWAY_3, // gateway || is generated through one (nested) for-loop, ||
61+
POST_DNS, POST_DNS_1, POST_DNS_2, POST_DNS_3, // DNS || all these 16 enum elements must be listed in succession!! ||
62+
POST_TCP, // TCP port || Because HTML code for these 3 ports ||
63+
POST_UDP, // UDP port || is generated through one for-loop, ||
64+
POST_WEB, // web UI port || these 3 elements must be listed in succession!! ||
65+
POST_RTU_OVER, // RTU over TCP/UDP
66+
POST_BAUD, // baud rate
67+
POST_DATA, // data bits
68+
POST_PARITY, // parity
69+
POST_STOP, // stop bits
70+
POST_TIMEOUT, // response timeout
71+
POST_RETRY, // retry attempts
72+
POST_ACTION // actions on Tools page
73+
};
74+
3275
void recvWeb()
3376
{
3477
EthernetClient client = webServer.available();
3578
if (client) {
36-
dbg(F("[web] Data from client "));
79+
dbg(F("[web in] Data from client "));
3780

3881
char uri[smallbuffersize]; // the requested page
3982
// char requestParameter[smallbuffersize]; // parameter appended to the URI after a ?
@@ -54,13 +97,13 @@ void recvWeb()
5497
{
5598
if (status == REQUEST) // read the first line
5699
{
57-
dbg(F("[web] webInBuffer=")); dbgln(webInBuffer);
100+
dbg(F("[web in] webInBuffer=")); dbgln(webInBuffer);
58101
// now split the input
59102
char *ptr;
60103
ptr = strtok(webInBuffer, " "); // strtok willdestroy the newRequest
61104
ptr = strtok(NULL, " ");
62105
strlcpy(uri, ptr, sizeof(uri)); // enthält noch evtl. parameter
63-
dbg(F("[web] uri=")); dbgln(uri);
106+
dbg(F("[web in] uri=")); dbgln(uri);
64107
status = EMPTY_LINE; // jump to next status
65108
}
66109
else if (status > REQUEST && i < 2) // check if we have an empty line
@@ -92,24 +135,35 @@ void recvWeb()
92135
}
93136
if (status == BODY) // status 3 could end without linefeed, therefore we takeover here also
94137
{
95-
dbg(F("[web] POST data=")); dbgln(webInBuffer);
138+
dbg(F("[web in] POST data=")); dbgln(webInBuffer);
96139
if (webInBuffer[0] != '\0') {
97140
processPost(webInBuffer);
98141
}
99142
}
100143
}
101144

102-
// send back a response
103-
// Get number of the requested page
145+
// Get number of the requested page from URI
104146
byte reqPage = 0; //requested page
105-
if ((uri[0] == '/') && !strcmp(uri + 2, ".htm")) {
106-
reqPage = uri[1] - 48; // Convert ASCII to byte
147+
if (!strcmp(uri, "/")) // the homepage Current Status
148+
reqPage = PAGE_STATUS;
149+
else if ((uri[0] == '/') && !strcmp(uri + 2, ".htm")) {
150+
reqPage = (byte)(uri[1] - 48); // Convert single ASCII char to byte
107151
}
108-
109152
// Actions that require "please wait" page
110153
if (action == WEB || action == REBOOT || action == ETH_SOFT || action == FACTORY || action == MAC) {
111-
sendPage(client, 0xFF); // Send "please wait" page
112-
// Do all actions before the "please wait" redirects (5s delay at the moment)
154+
reqPage = PAGE_WAIT;
155+
}
156+
157+
// Send page
158+
if ((reqPage > 0) && (reqPage <= PAGE_WAIT))
159+
sendPage(client, reqPage);
160+
else if (!strcmp(uri, "/favicon.ico")) // a favicon
161+
send204(client); // if you don't have a favicon, send 204
162+
else // if the page is unknown, HTTP response code 404
163+
send404(client); // defaults to 404 error
164+
165+
// Do all actions before the "please wait" redirects (5s delay at the moment)
166+
if (reqPage == PAGE_WAIT) {
113167
for (byte n = 0; n < maxSockNum; n++) {
114168
// in case of webserver restart, stop only clients from old webserver (clients with port different from current settings)
115169
EthernetClient clientTemp = EthernetClient(n);
@@ -119,7 +173,6 @@ void recvWeb()
119173
clientTemp.stop();
120174
}
121175
}
122-
123176
switch (action) {
124177
case WEB:
125178
webServer = EthernetServer(localConfig.webPort);
@@ -136,20 +189,10 @@ void recvWeb()
136189
break;
137190
}
138191
}
139-
else if (!strcmp(uri, "/") || !strcmp(uri, "/index.htm")) // the homepage Current Status
140-
sendPage(client, 1);
141-
else if ((reqPage > 0) && (reqPage <= pagesCnt))
142-
sendPage(client, reqPage);
143-
else if (!strcmp(uri, "/favicon.ico")) // a favicon
144-
send204(client); // if you don't have a favicon, send 204
145-
else // if the page is unknown, HTTP response code 404
146-
send404(client);
147192
action = NONE;
148-
dbg(F("[web] Stop client "));
149193
}
150194
}
151195

152-
153196
// This function stores POST parameter values in localConfig.
154197
// Most changes are saved and applied immediatelly, some changes (IP settings, web server port, actions in "Tools" page) are saved but applied later after "please wait" page is sent.
155198
void processPost(char postParameter[]) {
@@ -161,134 +204,108 @@ void processPost(char postParameter[]) {
161204
char *paramValue;
162205
char *sav2 = NULL; // for inner strtok_r
163206
paramKey = strtok_r(point, "=", &sav2); // inner strtok_r, use sav2
207+
paramValue = strtok_r(NULL, "=", &sav2);
208+
if (!paramValue) continue;
209+
byte paramKeyByte = atoi(paramKey);
210+
unsigned long paramValueUlong = atol(paramValue);
164211

165-
switch (paramKey[0]) {
166-
case 'i': // processing IP Settings
167-
{
168-
paramValue = strtok_r(NULL, "=", &sav2);
169-
int paramValueInt = atoi(paramValue);
170-
if (paramValue && (paramValueInt >= 0 && paramValueInt <= 255)) {
171-
byte j = atoi(paramKey + 1);
172-
if (j <= 0); // do nothing
173-
else if (j == 1) { // Enable DHCP
174-
if ((byte)paramValueInt != localConfig.enableDhcp) {
175-
action = ETH_SOFT;
176-
localConfig.enableDhcp = (byte)paramValueInt;
177-
}
178-
} else if (j <= 5) { // IP
179-
if ((byte)paramValueInt != localConfig.ip[j - 2]) {
180-
action = ETH_SOFT;
181-
localConfig.ip[j - 2] = (byte)paramValueInt;
182-
}
183-
} else if (j <= 9) { // Subnet
184-
if ((byte)paramValueInt != localConfig.subnet[j - 6]) {
185-
action = ETH_SOFT;
186-
localConfig.subnet[j - 6] = (byte)paramValueInt;
187-
}
188-
} else if (j <= 13) { // Gateway
189-
if ((byte)paramValueInt != localConfig.gateway[j - 10]) {
190-
action = ETH_SOFT;
191-
localConfig.gateway[j - 10] = (byte)paramValueInt;
192-
}
193-
} else if (j <= 17) { // DNS
194-
if ((byte)paramValueInt != localConfig.dns[j - 14]) {
195-
action = ETH_SOFT;
196-
localConfig.dns[j - 14] = (byte)paramValueInt;
197-
}
198-
}
199-
}
200-
break;
212+
switch (paramKeyByte) {
213+
case POST_NONE: // reserved, because atoi / atol returns NULL in case of error
214+
break;
215+
case POST_DHCP:
216+
if ((byte)paramValueUlong != localConfig.enableDhcp) {
217+
action = ETH_SOFT;
218+
localConfig.enableDhcp = (byte)paramValueUlong;
201219
}
202-
case 't': // processing TCP/UDP Settings
203-
{
204-
paramValue = strtok_r(NULL, "=", &sav2);
205-
unsigned int paramValueUint = atoi(paramValue);
206-
if (paramValue) {
207-
switch (atoi(paramKey + 1)) {
208-
case 1: // TCP port
209-
if (localConfig.tcpPort != paramValueUint) {
210-
for (byte i = 0; i < maxSockNum; i++) {
211-
EthernetClient clientTemp = EthernetClient(i);
212-
if (clientTemp.status() != SnSR::UDP && clientTemp.localPort() == localConfig.tcpPort) {
213-
clientTemp.flush();
214-
clientTemp.stop();
215-
}
216-
}
217-
localConfig.tcpPort = paramValueUint;
218-
modbusServer = EthernetServer(localConfig.tcpPort);
219-
}
220-
break;
221-
case 2: // UDP Port
222-
if (localConfig.udpPort != paramValueUint) {
223-
localConfig.udpPort = paramValueUint;
224-
Udp.stop();
225-
Udp.begin(localConfig.udpPort);
226-
}
227-
break;
228-
case 3: // Web Port
229-
if (localConfig.webPort != paramValueUint) {
230-
localConfig.webPort = paramValueUint;
231-
action = WEB;
232-
}
233-
break;
234-
case 4: // Enable Modbus RTU over TCP/UDP
235-
localConfig.enableRtuOverTcp = (byte)paramValueUint;
236-
break;
237-
default:
238-
break;
239-
}
240-
}
241-
break;
220+
break;
221+
case POST_IP ... POST_IP_3:
222+
if ((byte)paramValueUlong != localConfig.ip[paramKeyByte - POST_IP]) {
223+
action = ETH_SOFT;
224+
localConfig.ip[paramKeyByte - POST_IP] = (byte)paramValueUlong;
242225
}
243-
case 'r': // processing RS485 Settings
244-
{
245-
paramValue = strtok_r(NULL, "=", &sav2);
246-
unsigned long paramValueUlong = atol(paramValue);
247-
if (paramValue) {
248-
switch (atoi(paramKey + 1)) {
249-
case 1: // Baud
250-
if (localConfig.baud != paramValueUlong) {
251-
action = SERIAL_SOFT;
252-
localConfig.baud = paramValueUlong;
253-
}
254-
break;
255-
case 2: // Data Size
256-
if ((((localConfig.serialConfig & 0x06) >> 1) + 5) != atoi(paramValue)) {
257-
action = SERIAL_SOFT;
258-
localConfig.serialConfig = (localConfig.serialConfig & 0xF9) | ((atoi(paramValue) - 5) << 1);
259-
}
260-
break;
261-
case 3: // Parity
262-
if (((localConfig.serialConfig & 0x30) >> 4) != atoi(paramValue)) {
263-
action = SERIAL_SOFT;
264-
localConfig.serialConfig = (localConfig.serialConfig & 0xCF) | (atoi(paramValue) << 4);
265-
}
266-
break;
267-
case 4: // Stop Bits
268-
if ((((localConfig.serialConfig & 0x08) >> 3) + 1) != atoi(paramValue)) {
269-
action = SERIAL_SOFT;
270-
localConfig.serialConfig = (localConfig.serialConfig & 0xF7) | ((atoi(paramValue) - 1) << 3);
271-
}
272-
break;
273-
case 5: // Modbus timeout
274-
localConfig.serialTimeout = paramValueUlong;
275-
break;
276-
case 6: // Modbus retry
277-
localConfig.serialRetry = (byte)paramValueUlong;
278-
break;
279-
default:
280-
break;
226+
break;
227+
case POST_SUBNET ... POST_SUBNET_3:
228+
if ((byte)paramValueUlong != localConfig.subnet[paramKeyByte - POST_SUBNET]) {
229+
action = ETH_SOFT;
230+
localConfig.subnet[paramKeyByte - POST_SUBNET] = (byte)paramValueUlong;
231+
}
232+
break;
233+
case POST_GATEWAY ... POST_GATEWAY_3:
234+
if ((byte)paramValueUlong != localConfig.gateway[paramKeyByte - POST_GATEWAY]) {
235+
action = ETH_SOFT;
236+
localConfig.gateway[paramKeyByte - POST_GATEWAY] = (byte)paramValueUlong;
237+
}
238+
break;
239+
case POST_DNS ... POST_DNS_3:
240+
if ((byte)paramValueUlong != localConfig.dns[paramKeyByte - POST_DNS]) {
241+
action = ETH_SOFT;
242+
localConfig.dns[paramKeyByte - POST_DNS] = (byte)paramValueUlong;
243+
}
244+
break;
245+
case POST_TCP:
246+
if (localConfig.tcpPort != (unsigned int)paramValueUlong) {
247+
for (byte i = 0; i < maxSockNum; i++) {
248+
EthernetClient clientTemp = EthernetClient(i);
249+
if (clientTemp.status() != SnSR::UDP && clientTemp.localPort() == localConfig.tcpPort) {
250+
clientTemp.flush();
251+
clientTemp.stop();
281252
}
282253
}
283-
break;
254+
localConfig.tcpPort = (unsigned int)paramValueUlong;
255+
modbusServer = EthernetServer(localConfig.tcpPort);
284256
}
285-
case 'a': // processing Tools buttons
286-
{
287-
action = (action_type)atoi(strtok_r(NULL, "=", &sav2));
288-
break;
289-
default:
290-
break;
257+
break;
258+
case POST_UDP:
259+
if (localConfig.udpPort != (unsigned int)paramValueUlong) {
260+
localConfig.udpPort = (unsigned int)paramValueUlong;
261+
Udp.stop();
262+
Udp.begin(localConfig.udpPort);
263+
}
264+
break;
265+
case POST_WEB:
266+
if (localConfig.webPort != (unsigned int)paramValueUlong) {
267+
localConfig.webPort = (unsigned int)paramValueUlong;
268+
action = WEB;
269+
}
270+
break;
271+
case POST_RTU_OVER:
272+
localConfig.enableRtuOverTcp = (byte)paramValueUlong;
273+
break;
274+
case POST_BAUD:
275+
if (localConfig.baud != paramValueUlong) {
276+
action = SERIAL_SOFT;
277+
localConfig.baud = paramValueUlong;
278+
}
279+
break;
280+
case POST_DATA:
281+
if ((((localConfig.serialConfig & 0x06) >> 1) + 5) != (byte)paramValueUlong) {
282+
action = SERIAL_SOFT;
283+
localConfig.serialConfig = (localConfig.serialConfig & 0xF9) | (((byte)paramValueUlong - 5) << 1);
284+
}
285+
break;
286+
case POST_PARITY:
287+
if (((localConfig.serialConfig & 0x30) >> 4) != (byte)paramValueUlong) {
288+
action = SERIAL_SOFT;
289+
localConfig.serialConfig = (localConfig.serialConfig & 0xCF) | ((byte)paramValueUlong << 4);
291290
}
291+
break;
292+
case POST_STOP:
293+
if ((((localConfig.serialConfig & 0x08) >> 3) + 1) != (byte)paramValueUlong) {
294+
action = SERIAL_SOFT;
295+
localConfig.serialConfig = (localConfig.serialConfig & 0xF7) | (((byte)paramValueUlong - 1) << 3);
296+
}
297+
break;
298+
case POST_TIMEOUT:
299+
localConfig.serialTimeout = paramValueUlong;
300+
break;
301+
case POST_RETRY:
302+
localConfig.serialRetry = (byte)paramValueUlong;
303+
break;
304+
case POST_ACTION:
305+
action = (action_type)paramValueUlong;
306+
break;
307+
default:
308+
break;
292309
}
293310
point = strtok_r(NULL, "&", &sav1);
294311
} // while (point != NULL)

0 commit comments

Comments
 (0)