Last active
July 28, 2021 08:43
-
-
Save frodo821/ffae0b52c21232157f5dd1f9afc73640 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SHS_NETWORK_TIME_H_INCLUDED_ | |
#define SHS_NETWORK_TIME_H_INCLUDED_ | |
#include <WiFi.h> | |
#include <WiFiUdp.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <string> | |
#include <vector> | |
#include <iostream> | |
#include <iomanip> | |
#include <sstream> | |
#include "esp_system.h" | |
#include "shs_util.h" | |
#define NTP_SERVER_ADDR "ntp.nict.jp" | |
#define NTP_SERVER_PORT 123 | |
#define NTP_CLIENT_PORT 64321 | |
#define POLLING_INTERVAL 150 | |
#define MAX_POLLING 20 | |
#define SYNC_INTERVAL 60 | |
#define leap_indicator(packet) ((packet).li_vn_mode & 3) | |
#define version_number(packet) (((packet).li_vn_mode >> 2) & 7) | |
#define ntp_mode(packet) ((packet).li_vn_mode >> 5) | |
#define epoch_year(y) ((y)+1900) | |
#define leap_year(y) (!(epoch_year(y) % 400) || (!(epoch_year(y) % 4) && epoch_year(y) % 25)) | |
#define days_of_year(y) (leap_year(y)?366:365) | |
using namespace std; | |
typedef struct { | |
/* 01 */ uint8_t li_vn_mode; | |
/* 02 */ uint8_t stratum; | |
/* 03 */ uint8_t poll; | |
/* 04 */ uint8_t precision; | |
/* 08 */ uint32_t root_delay; | |
/* 0c */ uint32_t root_dispersion; | |
/* 10 */ uint32_t reference_id; | |
/* 14 */ uint32_t reference_timestamp; | |
/* 18 */ uint32_t reference_timestamp_frac; | |
/* 1c */ uint32_t origin_timestamp; | |
/* 20 */ uint32_t origin_timestamp_frac; | |
/* 24 */ uint32_t receive_timestamp; | |
/* 28 */ uint32_t receive_timestamp_frac; | |
/* 2c */ uint32_t transmit_timestamp; | |
/* 30 */ uint32_t transmit_timestamp_frac; | |
} NTPPacket; | |
typedef struct { | |
uint32_t timestamp_seconds; | |
uint32_t timestamp_seconds_frac; | |
uint8_t seconds; | |
uint8_t minutes; | |
uint8_t hours; | |
uint8_t day; | |
uint8_t month; | |
uint16_t year; | |
uint8_t day_in_week; | |
} Timestamp; | |
inline void get_day_in_month(Timestamp* tm); | |
inline void update_day_and_month_from_day_in_year(Timestamp* tm, uint32_t day_in_year); | |
int32_t startup_timestamp_seconds; | |
int32_t startup_timestamp_seconds_frac; | |
WiFiUDP udp; | |
void IRAM_ATTR sync_time() { | |
NTPPacket packet; | |
packet.li_vn_mode = 0x1b; | |
if(!udp.beginPacket(NTP_SERVER_ADDR, NTP_SERVER_PORT)) { | |
M5.Lcd.print("Failed to connect to the ntp server.\n"); | |
return; | |
} | |
udp.write((unsigned char*)&packet, sizeof(NTPPacket)); | |
udp.endPacket(); | |
uint8_t recv; | |
for (uint8_t i = 0; i < MAX_POLLING; i++) { | |
udp.parsePacket(); | |
recv = udp.available(); | |
if (recv == sizeof(NTPPacket)) | |
break; | |
delay(POLLING_INTERVAL); | |
} | |
if (recv != sizeof(NTPPacket)) { | |
return; | |
} | |
udp.read((unsigned char*)&packet, sizeof(NTPPacket)); | |
uint32_t ms = millis(); | |
startup_timestamp_seconds = htonl(packet.transmit_timestamp) - ms / 1000; | |
startup_timestamp_seconds_frac = (uint32_t)(((uint64_t)htonl(packet.transmit_timestamp_frac)) * 1000 / 0xFFFFFFFF - ms % 1000); | |
} | |
void get_current_time(Timestamp* tm) { | |
uint32_t ms = millis() + startup_timestamp_seconds_frac; | |
tm->timestamp_seconds = ms / 1000 + startup_timestamp_seconds; | |
tm->timestamp_seconds_frac = ms % 1000 + startup_timestamp_seconds_frac; | |
tm->seconds = (uint8_t)(tm->timestamp_seconds % 60); | |
tm->minutes = (uint8_t)((tm->timestamp_seconds / 60) % 60); | |
tm->hours = (uint8_t)((tm->timestamp_seconds / 3600) % 24); | |
get_day_in_month(tm); | |
} | |
inline void get_day_in_month(Timestamp* tm) { | |
uint32_t all_days_since_epoch = tm->timestamp_seconds / 86400; | |
uint8_t year = 0; | |
uint32_t day = 0; | |
for(; (day+=days_of_year(year)) <= all_days_since_epoch; year++); | |
tm->year = year; | |
day = (uint8_t)(all_days_since_epoch - (day - days_of_year(year))); | |
tm->day_in_week = (all_days_since_epoch+1) % 7; | |
update_day_and_month_from_day_in_year(tm, day); | |
tm->year = epoch_year(year); | |
} | |
inline void update_day_and_month_from_day_in_year(Timestamp* tm, uint32_t day_in_year) { | |
uint8_t leap = leap_year(tm->year); | |
if(day_in_year < 31) { | |
tm->month = 1; | |
tm->day = (uint8_t)(day_in_year + 1); | |
return; | |
} | |
if((leap && day_in_year < 60) || day_in_year < 59) { | |
tm->month = 2; | |
tm->day = (uint8_t)(day_in_year - 30); | |
return; | |
} | |
if(leap) { | |
day_in_year--; | |
} | |
if(day_in_year < 90) { | |
tm->month = 3; | |
tm->day = (uint8_t)(day_in_year - 58); | |
return; | |
} | |
if(day_in_year < 120) { | |
tm->month = 4; | |
tm->day = (uint8_t)(day_in_year - 89); | |
return; | |
} | |
if(day_in_year < 151) { | |
tm->month = 5; | |
tm->day = (uint8_t)(day_in_year - 119); | |
return; | |
} | |
if(day_in_year < 181) { | |
tm->month = 6; | |
tm->day = (uint8_t)(day_in_year - 150); | |
return; | |
} | |
if(day_in_year < 212) { | |
tm->month = 7; | |
tm->day = (uint8_t)(day_in_year - 180); | |
return; | |
} | |
if(day_in_year < 243) { | |
tm->month = 8; | |
tm->day = (uint8_t)(day_in_year - 211); | |
return; | |
} | |
if(day_in_year < 273) { | |
tm->month = 9; | |
tm->day = (uint8_t)(day_in_year - 242); | |
return; | |
} | |
if(day_in_year < 304) { | |
tm->month = 10; | |
tm->day = (uint8_t)(day_in_year - 272); | |
return; | |
} | |
if(day_in_year < 334) { | |
tm->month = 11; | |
tm->day = (uint8_t)(day_in_year - 303); | |
return; | |
} | |
tm->month = 12; | |
tm->day = (uint8_t)(day_in_year - 333); | |
} | |
string time_format(Timestamp* t) { | |
ostringstream ss; | |
ss << setfill('0'); | |
switch(t->day_in_week) { | |
case 0: | |
ss << "Sun, "; | |
break; | |
case 1: | |
ss << "Mon, "; | |
break; | |
case 2: | |
ss << "Tue, "; | |
break; | |
case 3: | |
ss << "Wed, "; | |
break; | |
case 4: | |
ss << "Thu, "; | |
break; | |
case 5: | |
ss << "Fri, "; | |
break; | |
case 6: | |
ss << "Sat, "; | |
break; | |
default: | |
goto err; | |
} | |
ss << setw(2) << (int)t->day; | |
switch(t->month) { | |
case 1: | |
ss << " Jan "; | |
break; | |
case 2: | |
ss << " Feb "; | |
break; | |
case 3: | |
ss << " Mar "; | |
break; | |
case 4: | |
ss << " Apr "; | |
break; | |
case 5: | |
ss << " May "; | |
break; | |
case 6: | |
ss << " Jun "; | |
break; | |
case 7: | |
ss << " Jul "; | |
break; | |
case 8: | |
ss << " Aug "; | |
break; | |
case 9: | |
ss << " Sep "; | |
break; | |
case 10: | |
ss << " Oct "; | |
break; | |
case 11: | |
ss << " Nov "; | |
break; | |
case 12: | |
ss << " Dec "; | |
break; | |
default: | |
goto err; | |
} | |
ss << setw(4) << t->year << " "; | |
ss << setw(2) << (int)t->hours << ":"; | |
ss << setw(2) << (int)t->minutes << ":"; | |
ss << setw(2) << (int)t->seconds << " GMT"; | |
return string(ss.str()); | |
err: | |
return string("malformed timestamp"); | |
} | |
void init_time() { | |
udp.begin(WiFi.localIP(), NTP_CLIENT_PORT); | |
sync_time(); | |
hw_timer_t *timer = timerBegin(0, 40000, true); | |
timerAttachInterrupt(timer, &sync_time, true); | |
timerAlarmWrite(timer, SYNC_INTERVAL * 2000, true); | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SHS_UTIL_H_INCLUDED_ | |
#define SHS_UTIL_H_INCLUDED_ | |
#include <algorithm> | |
#include <limits> | |
#include <stdlib.h> | |
using namespace std; | |
string to_string(int val) | |
{ | |
char buffer[numeric_limits<int>::digits10 + 1 + 2]; | |
sprintf(buffer, "%d", val); | |
return buffer; | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SIMPLE_HTTP_SERVER_H_INCLUDED_ | |
#define SIMPLE_HTTP_SERVER_H_INCLUDED_ | |
#include <map> | |
#include "shs_util.h" | |
#include <WiFi.h> | |
using namespace std; | |
typedef std::map<string, string> Headers; | |
typedef struct { | |
string protocol; | |
string path; | |
string meth; | |
Headers headers; | |
string body; | |
} Request; | |
typedef struct { | |
int status_code; | |
string status_message; | |
Headers headers; | |
string content; | |
string content_type; | |
} Response; | |
void print_access_log(WiFiClient* client, Request* req, Response* resp) { | |
M5.Lcd.print(client->remoteIP()); | |
M5.Lcd.print(": "); | |
M5.Lcd.print(req->meth.c_str()); | |
M5.Lcd.print(" "); | |
M5.Lcd.print(req->path.c_str()); | |
M5.Lcd.print(" "); | |
M5.Lcd.print(req->protocol.c_str()); | |
M5.Lcd.print(" -> "); | |
M5.Lcd.print(to_string(resp->status_code).c_str()); | |
M5.Lcd.print("\n"); | |
} | |
int parse_http(WiFiClient* client, Request* req) { | |
int bytes = 0; | |
int state = 0; | |
while(client->connected()) { | |
int next = 0; | |
if (state == 2) { | |
if (bytes) { | |
char* buf = (char*)malloc(bytes); | |
client->readBytes(buf, bytes); | |
req->body = buf; | |
free(buf); | |
} | |
return 0; | |
} | |
String buf; | |
if(client->available()) { | |
buf = client->readStringUntil('\n'); | |
next = 1; | |
} | |
int len = buf.length(); | |
switch(state) { | |
case 0: | |
{ | |
int reqline_state = 0; | |
for(int i = 0; i < len; i++) { | |
if(buf.charAt(i) == ' ') { | |
reqline_state++; | |
continue; | |
} | |
switch(reqline_state) { | |
case 0: | |
req->meth += toupper(buf.charAt(i)); | |
continue; | |
case 1: | |
req->path += buf.charAt(i); | |
continue; | |
case 2: | |
req->protocol += buf.charAt(i); | |
continue; | |
case 3: | |
return -1; | |
} | |
} | |
break; | |
} | |
case 1: | |
{ | |
int header_state = 0; | |
string hname = ""; | |
string value = ""; | |
if (len == 0) { | |
state++; | |
continue; | |
} | |
for(int i = 0; i < len; i++) { | |
if(header_state && buf.charAt(i) == ':') { | |
header_state++; | |
while (i + 1 < len && buf.charAt(i + 1) == ' ') { | |
i++; | |
} | |
continue; | |
} | |
switch(header_state) { | |
case 0: | |
hname += buf.charAt(i); | |
continue; | |
case 1: | |
value += buf.charAt(i); | |
continue; | |
} | |
} | |
std::transform(hname.cbegin(), hname.cend(), hname.begin(), ::tolower); | |
req->headers[hname] = value; | |
if (hname == "content-length") { | |
bytes = atoi(value.c_str()); | |
} | |
continue; | |
} | |
} | |
if (next) { | |
state++; | |
} | |
} | |
return 0; | |
} | |
void response(WiFiClient* client, Response* resp) { | |
if (!resp->content.empty()) { | |
int bytes = resp->content.length(); | |
resp->headers["content-type"] = resp->content_type.empty() ? "text/plain" : resp->content_type; | |
resp->headers["content-length"] = to_string(bytes); | |
} | |
resp->headers["connection"] = "close"; | |
resp->headers["server"] = "Arduino HTTP/0.1 (ESP32)"; | |
Timestamp tm; | |
get_current_time(&tm); | |
resp->headers["date"] = time_format(&tm); | |
client->write("HTTP/1.1 "); | |
client->write(to_string(resp->status_code).c_str()); | |
client->write(" "); | |
client->write(resp->status_message.c_str()); | |
client->write("\n"); | |
for(auto i = resp->headers.begin(); i != resp->headers.end() ; ++i) { | |
client->write(i->first.c_str()); | |
client->write(": "); | |
client->write(i->second.c_str()); | |
client->write("\n"); | |
} | |
client->write("\n"); | |
client->write(resp->content.c_str()); | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define W_PASSWORD "wifi-password" | |
#define W_SSID "wifi-ssid" | |
#define HOST "0.0.0.0" | |
#define PORT 80 | |
#include <M5Stack.h> | |
#undef min | |
#include "shs_network_time.h" | |
#include "simplehttpserver.h" | |
WiFiServer server(PORT); | |
void handle_http_request(Request* request, Response* resp) { | |
if (request->path != "/") { | |
resp->content_type = "text/html"; | |
resp->content = "<h1>404 Not Found.</h1>\n"; | |
resp->status_code = 404; | |
resp->status_message = "Not Found"; | |
return; | |
} | |
if (request->meth == "GET") { | |
resp->content_type = "text/html"; | |
resp->content = "<h1>It works!</h1>\n"; | |
resp->status_code = 200; | |
resp->status_message = "OK"; | |
return; | |
} | |
resp->content_type = "text/html"; | |
resp->content = "<h1>405 "; | |
resp->content += request->meth; | |
resp->content += ": Method Not Allowed.</h1>\n"; | |
resp->status_code = 405; | |
resp->status_message = "Method Not Allowed"; | |
return; | |
} | |
void setup() { | |
M5.begin(); | |
WiFi.disconnect(true); | |
WiFi.mode(WIFI_STA); | |
WiFi.begin(W_SSID, W_PASSWORD); | |
M5.Lcd.print("Connecting to the network"); | |
for (int counter = 0; WiFi.status() != WL_CONNECTED; counter++) { | |
delay(500); | |
M5.Lcd.print("."); | |
if (counter > 60) { | |
M5.Lcd.print("\nFailed to connect to the network."); | |
delay(1000); | |
ESP.restart(); | |
} | |
} | |
M5.Lcd.print("\n"); | |
M5.Lcd.print("WiFi connected\n"); | |
M5.Lcd.print("IP address set: "); | |
M5.Lcd.print(WiFi.localIP()); | |
M5.Lcd.print("\n"); | |
M5.Lcd.print("ESP Mac Address: "); | |
M5.Lcd.print(WiFi.macAddress()); | |
M5.Lcd.print("\n"); | |
M5.Lcd.print("Subnet Mask: "); | |
M5.Lcd.print(WiFi.subnetMask()); | |
M5.Lcd.print("\n"); | |
M5.Lcd.print("Gateway IP: "); | |
M5.Lcd.print(WiFi.gatewayIP()); | |
M5.Lcd.print("\n"); | |
M5.Lcd.print("DNS: "); | |
M5.Lcd.print(WiFi.dnsIP()); | |
M5.Lcd.print("\n"); | |
M5.Lcd.print("Initializing timestamp syncing\n"); | |
init_time(); | |
server.begin(); | |
Timestamp tm; | |
get_current_time(&tm); | |
M5.Lcd.print("server started on 0.0.0.0:80 at "); | |
M5.Lcd.print(time_format(&tm).c_str()); | |
M5.Lcd.print("\n"); | |
} | |
void loop() { | |
Request req; | |
Response resp; | |
WiFiClient client = server.available(); | |
if(!client) { | |
return; | |
} | |
parse_http(&client, &req); | |
handle_http_request(&req, &resp); | |
response(&client, &resp); | |
print_access_log(&client, &req, &resp); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment