Skip to content

Commit adcdb97

Browse files
James Fosterjgfoster
James Foster
andauthored
Support basic HTTP get file request (#254)
* * Add newline header to startup (it sometimes appears on a continuation line) * Print SD card directory on startup * Add all serial output to the SD card as date.log * Sample requests: - http://192.168.1.156/echo?value="foo" - http://192.168.1.156/20210825.log * spelling * more spelling * spelling expects 'yy' * * Use `int` for freeMemory result. * Add some commented-out debugging logging. * Merge from main with passing tests. * Reduce memory usage. * Tests pass. * Use larger buffer to send files. * remove commented-out code * Address some Codacy warnings. Co-authored-by: James Foster <[email protected]>
1 parent d715667 commit adcdb97

10 files changed

+243
-42
lines changed

.github/actions/spelling/expect.txt

+8
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ avr
88
Calib
99
cassert
1010
cfg
11+
charset
1112
chr
1213
chrono
1314
ci
1415
Clib
1516
cmake
17+
concat
1618
config
1719
cout
1820
cpp
@@ -71,6 +73,7 @@ len
7173
LGPL
7274
libgtk
7375
libtc
76+
localhost
7477
localtime
7578
lowpoint
7679
LOWTHRESH
@@ -79,6 +82,7 @@ makedirs
7982
mday
8083
markdownlint
8184
mega
85+
memcmp
8286
memset
8387
microcontroler
8488
milli
@@ -128,13 +132,15 @@ solonoids
128132
src
129133
strcpy
130134
strlen
135+
strncmp
131136
strnlen
132137
strncpy
133138
substr
134139
sudo
135140
sys
136141
tankid
137142
testfile
143+
Thu
138144
timeinfo
139145
Tmp
140146
TODO
@@ -147,6 +153,7 @@ unittest
147153
unixtime
148154
unshallow
149155
url
156+
UTF
150157
vfprintf
151158
vprintf
152159
vscode
@@ -161,3 +168,4 @@ wxpython
161168
xslx
162169
yaml
163170
yml
171+
yy

src/Devices/EthernetServer_TC.cpp

+162-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#include "Devices/EthernetServer_TC.h"
22

3+
#include <avr/wdt.h>
4+
5+
#include "DateTime_TC.h"
6+
#include "SD_TC.h"
37
#include "Serial_TC.h"
8+
#include "TankControllerLib.h"
49

510
// class variables
611
EthernetServer_TC* EthernetServer_TC::_instance = nullptr;
@@ -22,24 +27,164 @@ EthernetServer_TC* EthernetServer_TC::instance() {
2227
*/
2328
EthernetServer_TC::EthernetServer_TC(uint16_t port) : EthernetServer(port) {
2429
begin();
25-
serial(F("Ethernet Server is listening on port 80 of ??????"));
30+
serial(F("Ethernet Server is listening on port 80"));
2631
}
2732

28-
/**
29-
* Look for request from external client and handle it.
30-
* This (incomplete) code copied from HandleRequest()
31-
*/
32-
void EthernetServer_TC::handleRequest() {
33-
// listen for incoming clients
34-
EthernetClient rpc_client = available(); // Raspberry Pi Client
35-
if (rpc_client) {
36-
// TODO: NEED TO IMPLEMENT
37-
// HandleRequest(rpc_client);
38-
39-
// give the web browser time to receive the data
40-
// delay(ONE_SECOND_DELAY_IN_MILLIS);
41-
// close the connection:
42-
rpc_client.stop();
43-
serial(F("rpc_client disconnected"));
33+
void EthernetServer_TC::echo() {
34+
serial(F("echo() - \"%s\""), buffer + 19);
35+
int i = 19;
36+
while (buffer[i] != ' ' && buffer[i] != '\0') {
37+
++i;
38+
}
39+
serial(F("echo() found space or null at %d"), i);
40+
if (memcmp_P(buffer + i - 3, F("%22"), 3)) {
41+
serial(F("bad"));
42+
} else {
43+
buffer[i - 3] = '\0';
44+
serial(F("echo \"%s\""), buffer + 19);
45+
sendHeadersWithSize(strnlen(buffer + 19, sizeof(buffer) - 20));
46+
client.write(buffer + 19);
47+
client.stop();
48+
state = NOT_CONNECTED;
49+
}
50+
}
51+
52+
bool EthernetServer_TC::file() {
53+
// Buffer has something like "GET /path HTTP/1.1"
54+
// and we want to put a null at the end of the path.
55+
int i = 4;
56+
while (buffer[i] != ' ') {
57+
++i;
58+
}
59+
buffer[i] = '\0';
60+
// Look for file in SD card.
61+
if (!SD_TC::instance()->exists(buffer + 4)) {
62+
serial(F("file - \"%s\" not found!"), buffer + 4);
63+
return false;
64+
}
65+
// Open file and send headers with file size.
66+
File file = SD_TC::instance()->open(buffer + 4);
67+
uint32_t size = file.size();
68+
serial(F("file \"%s\" has a size of %lu"), buffer + 4, size);
69+
sendHeadersWithSize(size);
70+
// Send file contents
71+
uint32_t totalBytes = 0;
72+
uint32_t timeInRead = 0;
73+
uint32_t timeInWrite = 0;
74+
uint32_t timeInFlush = 0;
75+
76+
wdt_disable();
77+
uint32_t flushCount = 0;
78+
while (file.available32()) {
79+
uint32_t startTime = millis();
80+
int readSize = file.read(buffer, sizeof(buffer)); // Flawfinder: ignore
81+
timeInRead += millis() - startTime;
82+
startTime = millis();
83+
int writeSize = client.write(buffer, readSize);
84+
timeInWrite += millis() - startTime;
85+
if (writeSize != readSize) {
86+
serial(F("totalBytes = %lu; read = %d; write = %d"), totalBytes, readSize, writeSize);
87+
break;
88+
}
89+
totalBytes += writeSize;
90+
if (totalBytes % 8196 == 0) {
91+
startTime = millis();
92+
client.flush();
93+
timeInFlush += millis() - startTime;
94+
++flushCount;
95+
}
96+
}
97+
file.close();
98+
client.stop();
99+
state = NOT_CONNECTED;
100+
serial(F("write = %lu; freeMemory = %i"), totalBytes, TankControllerLib::instance()->freeMemory());
101+
serial(F("timeInRead = %lu; timeInWrite = %lu; timeInFlush = %lu"), timeInRead, timeInWrite, timeInFlush);
102+
wdt_enable(WDTO_8S);
103+
return true;
104+
}
105+
106+
void EthernetServer_TC::get() {
107+
if (memcmp_P(buffer + 4, F("/echo?value=%22"), 15) == 0) {
108+
echo();
109+
} else if (!file()) {
110+
// TODO: send an error response
111+
serial(F("get \"%s\" not recognized!"), buffer + 4);
112+
client.stop();
113+
state = NOT_CONNECTED;
114+
}
115+
}
116+
117+
void EthernetServer_TC::loop() {
118+
if (client || (client = accept())) { // if we have a connection
119+
if (state == NOT_CONNECTED) {
120+
state = READ_REQUEST;
121+
bufferContentsSize = 0;
122+
connectedAt = millis(); // record start time (so we can do timeout)
123+
}
124+
// read request
125+
int next;
126+
while (state == READ_REQUEST && bufferContentsSize < sizeof(buffer) &&
127+
(next = client.read()) != -1) { // Flawfinder: ignore
128+
buffer[bufferContentsSize++] = (char)(next & 0xFF);
129+
if (bufferContentsSize > 1 && buffer[bufferContentsSize - 2] == '\r' && buffer[bufferContentsSize - 1] == '\n') {
130+
buffer[bufferContentsSize - 2] = '\0';
131+
state = HAS_REQUEST;
132+
if (memcmp_P(buffer, F("GET "), 4) == 0) {
133+
state = GET_REQUEST;
134+
break;
135+
}
136+
break;
137+
}
138+
}
139+
switch (state) {
140+
case GET_REQUEST:
141+
get();
142+
break;
143+
default:
144+
break;
145+
}
146+
} else if (state != NOT_CONNECTED) { // existing connection has been closed
147+
state = NOT_CONNECTED;
148+
bufferContentsSize = 0;
149+
client.stop();
150+
connectedAt = 0;
151+
} else {
152+
// no client and not recently connected
44153
}
45154
}
155+
156+
void EthernetServer_TC::sendHeadersWithSize(uint32_t size) {
157+
char buffer[128];
158+
static const char response[] PROGMEM =
159+
"HTTP/1.1 200 OK\r\n"
160+
"Content-Type: text/plain;charset=UTF-8\r\n"
161+
"Content-Encoding: identity\r\n"
162+
"Content-Language: en-US\r\n";
163+
strncpy_P(buffer, (PGM_P)response, sizeof(buffer));
164+
client.write(buffer);
165+
snprintf_P(buffer, sizeof(buffer), (PGM_P)F("Content-Length: %lu\r\n"), (unsigned long)size);
166+
client.write(buffer);
167+
168+
// TODO: add "Date: " header
169+
// const __FlashStringHelper* weekdays[] = {F("Sun"), F("Mon"), F("Tue"), F("Wed"), F("Thu"), F("Fri"), F("Sat")};
170+
// const __FlashStringHelper* months[] = {F("Jan"), F("Feb"), F("Mar"), F("Apr"), F("May"), F("Jun"),
171+
// F("Jul"), F("Aug"), F("Sep"), F("Oct"), F("Nov"), F("Dec")};
172+
// DateTime_TC now = DateTime_TC::now();
173+
// int weekday = weekday(now.getYear(), now.getMonth(), now.getDay());
174+
// snprintf_P(buffer, sizeof(buffer), F("Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n"), );
175+
176+
// blank line indicates end of headers
177+
client.write('\r');
178+
client.write('\n');
179+
}
180+
181+
int EthernetServer_TC::weekday(int year, int month, int day) {
182+
// Calculate day of week in proleptic Gregorian calendar. Sunday == 0.
183+
int adjustment, mm, yy;
184+
if (year < 2000)
185+
year += 2000;
186+
adjustment = (14 - month) / 12;
187+
mm = month + 12 * adjustment - 2;
188+
yy = year - adjustment;
189+
return (day + (13 * mm - 1) / 5 + yy + yy / 4 - yy / 100 + yy / 400) % 7;
190+
}

src/Devices/EthernetServer_TC.h

+18-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <Ethernet.h>
88
#endif
99

10+
enum serverState_t { NOT_CONNECTED, READ_REQUEST, GET_REQUEST, HAS_REQUEST };
11+
1012
/**
1113
* EthernetServer_TC provides wrapper for web server for TankController
1214
*
@@ -20,12 +22,26 @@ class EthernetServer_TC : public EthernetServer {
2022
const char* className() const {
2123
return "EthernetServer_TC";
2224
}
23-
void handleRequest();
25+
void loop();
2426

2527
private:
2628
// class variables
2729
static EthernetServer_TC* _instance;
2830

29-
// instance methods
31+
// instance variables
32+
EthernetClient client;
33+
serverState_t state = NOT_CONNECTED;
34+
char buffer[1024];
35+
int bufferContentsSize = 0;
36+
unsigned long connectedAt = 0;
37+
38+
// instance methods: constructor
3039
EthernetServer_TC(uint16_t port);
40+
// instance methods: utility
41+
void sendHeadersWithSize(uint32_t size);
42+
int weekday(int year, int month, int day);
43+
// instance methods: HTTP
44+
void echo();
45+
bool file();
46+
void get();
3147
};

src/Devices/PushingBox.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ void PushingBox::sendData() {
6767
}
6868
char buffer[200];
6969
if (TankControllerLib::instance()->isInCalibration()) {
70-
char format[] PROGMEM =
70+
static const char format[] PROGMEM =
7171
"GET /pushingbox?devid=%s&tankid=%i&tempData=C&pHdata=C HTTP/1.1\r\n"
7272
"Host: api.pushingbox.com\r\n"
7373
"Connection: close\r\n"
7474
"\r\n";
7575
snprintf_P(buffer, sizeof(buffer), (PGM_P)format, DevID, tankID);
7676
} else {
77-
char format[] PROGMEM =
77+
static const char format[] PROGMEM =
7878
"GET /pushingbox?devid=%s&tankid=%i&tempData=%.2f&pHdata=%.3f HTTP/1.1\r\n"
7979
"Host: api.pushingbox.com\r\n"
8080
"Connection: close\r\n"

src/Devices/PushingBox.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class PushingBox {
2626
EthernetClient client;
2727
const char *DevID = nullptr; // DeviceID assigned by PushingBox and passed-in from TankController.ino
2828
// wait a bit for first reading (https://github.com/Open-Acidification/TankController/issues/179)
29-
unsigned long nextSendTime = 70000;
29+
uint32_t nextSendTime = 70000;
3030
const char *server = "api.pushingbox.com";
3131

3232
// instance method

src/Devices/SD_TC.cpp

+1-4
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,8 @@ File SD_TC::open(const char* path, oflag_t oflag) {
9292
return sd.open(path, oflag);
9393
}
9494

95-
/**
96-
* print the root directory and all subdirectories
97-
*/
9895
void SD_TC::printRootDirectory() {
99-
// serial(F("SD_TC::printRootDirectory()"));
96+
sd.ls(LS_DATE | LS_SIZE | LS_R);
10097
}
10198

10299
void SD_TC::todaysDataFileName(char* path, int size) {

src/Devices/Serial_TC.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ void Serial_TC::vprintf(const __FlashStringHelper *format, va_list args) {
5151
// need to avoid recursion since SD_TC could call serial()
5252
if (!printIsActive) {
5353
printIsActive = true;
54-
// this seems to cause problems on the actual hardware
55-
// SD_TC::instance()->appendToLog(buffer);
54+
SD_TC::instance()->appendToLog(buffer);
5655
printIsActive = false;
5756
}
5857
#ifdef MOCK_PINS_COUNT

0 commit comments

Comments
 (0)