Skip to content

Instantly share code, notes, and snippets.

@frodo821
Last active July 28, 2021 08:43
Show Gist options
  • Save frodo821/ffae0b52c21232157f5dd1f9afc73640 to your computer and use it in GitHub Desktop.
Save frodo821/ffae0b52c21232157f5dd1f9afc73640 to your computer and use it in GitHub Desktop.
#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
#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
#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
#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