Skip to content

Commit

Permalink
New web page
Browse files Browse the repository at this point in the history
remove upload_port = /dev/ttyUSB0 from ini file
  • Loading branch information
Sorin Stoiana authored and jgstroud committed Jan 8, 2024
1 parent 6f43561 commit 73860af
Show file tree
Hide file tree
Showing 9 changed files with 662 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ compile_commands.json
__pycache__
.DS_Store
.vscode
src/www/build
.vscode/*
93 changes: 93 additions & 0 deletions build_web_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
#
# This script converts standard web content files (html, css, etc) into a C++ language
# header file that is included in the program body. The files are compressed and use
# PROGMEM keyword to store in Flash to save RAM.
# Requires: gzip, xxd and sed.
#
# Copyright (c) 2023 David Kerr, https://github.com/dkerr64
#
import os
import subprocess
import shutil

sourcepath = "src/www"
targetpath = sourcepath + "/build"

filenames = next(os.walk(sourcepath), (None, None, []))[2]
print("Compressing and converting files from " + sourcepath + " into " + targetpath)
print(filenames)

# Start by deleting the target directory, then creating empty one.
try:
shutil.rmtree(targetpath)
except FileNotFoundError:
pass
os.mkdir(targetpath)

# Open webcontent file and write warning header...
wf = open(targetpath + "/webcontent.h", "w")
wf.write("/**************************************\n")
wf.write(" * Autogenerated DO NOT EDIT\n")
wf.write(" **************************************/\n")
wf.write("#include \"Arduino.h\"\n")
wf.write("#include <string>\n")
wf.flush()

varnames = []
# now loop through each file...
for file in filenames:
gzfile = targetpath + "/" + file + ".gz"
varnames.append(("/" + file, gzfile.replace(".", "_").replace("/", "_")))
f = open(gzfile, "w")
subprocess.run(["gzip", "-c", sourcepath + "/" + file], stdout=f)
f.close()
subprocess.run(["xxd", "-i", gzfile], stdout=wf)

wf.flush()

# Add possible MIME types to the file...
wf.write(
"""
char type_svg[] = "image/svg+xml";
char type_bmp[] = "image/bmp";
char type_gif[] = "image/gif";
char type_jpeg[] = "image/jpeg";
char type_jpg[] = "image/jpeg";
char type_png[] = "image/png";
char type_tiff[] = "image/tiff";
char type_tif[] = "image/tiff";
char type_txt[] = "text/plain";
char type_htm[] = "text/html";
char type_html[] = "text/html";
char type_css[] = "text/css";
char type_js[] = "text/javascript";
char type_mjs[] = "text/javascript";
char type_json[] = "application/json";
"""
)

# Use an unordered_map so we can lookup the data, length and type based on filename...
wf.write(
"std::unordered_map<std::string, std::tuple<unsigned char *, unsigned int, char *>> webcontent = {"
)
n = 0
for file, var in varnames:
t = file.rpartition(".")[-1]
# Need comma at end of every line except last one...
if n > 0:
wf.write(",")
wf.write('\n { "' + file + '", {' + var + ", " + var + "_len, type_" + t + "} }")
n = n + 1

# All done, close the file...
wf.write("\n};\n")
wf.close()

# Add the PROGMEM keyword to tell system to leave contents in Flash and not load into RAM...
subprocess.run(
["sed", "-i", "s/_gz\[\]/_gz\[\] PROGMEM/g", targetpath + "/webcontent.h"]
)

print("processed " + str(len(varnames)) + " files")
1 change: 1 addition & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ lib_deps =
https://github.com/ratgdo/espsoftwareserial.git#autobaud
lib_ldf_mode = deep+
extra_scripts =
pre:build_web_content.py
pre:auto_firmware_version.py

[env:native]
Expand Down
227 changes: 167 additions & 60 deletions src/web.cpp
Original file line number Diff line number Diff line change
@@ -1,105 +1,212 @@
// Copyright 2023 Brandon Matthews <thenewwazoo@optimaltour.us>
// Copyright (c) 2023-24 David Kerr, https://github.com/dkerr64
// All rights reserved. GPLv3 License

#include "www/build/webcontent.h"

#include <arduino_homekit_server.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
#include "log.h"
#include "ratgdo.h"

#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>

ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdater(true);

/********* forward decl *********/

void handle_root();
void handle_reset();
void handle_reboot();
void handle_notfound();
void handle_handlestatus();

extern struct GarageDoor garage_door;

// Make device_name available
extern "C" char device_name[];

/********* main loop **********/

void setup_web() {
server.on("/", HTTP_GET, handle_root);
void setup_web()
{
server.on("/status.json", HTTP_GET, handle_handlestatus);
server.on("/reset", HTTP_POST, handle_reset);
server.on("/reboot", HTTP_POST, handle_reboot);

server.onNotFound([]() {
server.send(404, "text/plain", "404: Not Found");
});

server.onNotFound(handle_notfound);

httpUpdater.setup(&server);

server.begin();
RINFO("HTTP server started");
}

void web_loop() {
void web_loop()
{
server.handleClient();
}

/********* handlers **********/

void handle_root() {
if (homekit_is_paired()) {
server.send(
200,
"text/html",
"<p>If you wish to re-pair to another HomeKit Home, you must first click the following button:</p>"
"<form action=\"/reset\" method=\"POST\">"
"<input type=\"submit\" value=\"Un-pair HomeKit\">"
"</form>"
"<p>To reboot the RATGDO, click the following button:</p>"
"<form action=\"/reboot\" method=\"POST\">"
"<input type=\"submit\" value=\"Reboot RATGDO\">"
"</form>"
"<p>Current Firmware Version: v" AUTO_VERSION "</p>"
"<form action=\"/update\">"
"<input type=\"submit\" value=\"Update Firmware\">"
"</form>");
} else {
server.send(
200,
"text/html",
"<p>Scan the following QR code with your iOS device to pair with HomeKit:</p>"
#include "qrcode.h"
"<p>If you wish to re-pair to another HomeKit Home, you must first click the following button:</p>"
"<form action=\"/reset\" method=\"POST\">"
"<input type=\"submit\" value=\"Un-pair HomeKit\">"
"</form>"
"<p>To reboot the RATGDO, click the following button:</p>"
"<form action=\"/reboot\" method=\"POST\">"
"<input type=\"submit\" value=\"Reboot RATGDO\">"
"</form>"
"<p>Current Firmware Version: v" AUTO_VERSION "</p>"
"<form action=\"/update\">"
"<input type=\"submit\" value=\"Update Firmware\">"
"</form>");
}
}

void handle_reset() {
void handle_reset()
{
RINFO("... reset requested");
homekit_storage_reset();

server.send(
200,
"text/html",
"<p>This device has been un-paired from HomeKit.</p>"
"<p><a href=\"/\">Back</a></p>"
);
"<p><a href=\"/\">Back</a></p>");
}

void handle_reboot() {
void handle_reboot()
{
RINFO("... reboot requested");
server.send(
200,
"text/html",
"<head>"
"<meta http-equiv=\"refresh\" content=\"15;url=/\" />"
"<meta http-equiv=\"refresh\" content=\"15;url=/\" />"
"</head>"
"<body>"
"<p>RATGDO restarting. Please wait. Reconnecting in 15 seconds...</p>"
"<p><a href=\"/\">Back</a></p>"
"</body>"
);
server.stop(); // ensure that delivery is complete?
delay(10); // give a bit of time for the connection to close fully
"<p>RATGDO restarting. Please wait. Reconnecting in 15 seconds...</p>"
"<p><a href=\"/\">Back</a></p>"
"</body>");

server.stop();
delay(10); // give a bit of time for the connection to close fully
ESP.restart();
}

void handle_notfound()
{
String page = server.uri();

if (page == "/")
{
page = "/index.html";
}

if (webcontent.count(page.c_str()) > 0)
{
unsigned char *data;
unsigned int length;
char *type;
std::tie(data, length, type) = webcontent[page.c_str()];
RINFO("Sending gzip data for: %s (type %s, length %i)", page.c_str(), type, length);
server.sendHeader("Content-Encoding", "gzip");
server.send_P(200, type, (const char *)data, length);
}
else
{
RINFO("Sending 404 not found for: %s", page.c_str());
server.send(404, "text/plain", "404: Not Found");
}
return;
}

void handle_handlestatus()
{
homekit_server_t *hks = arduino_homekit_get_running_server();

// If arguments passed with the URL then only a subset of the
// status fields are requested...
std::unordered_map<std::string, bool> argReq;
bool all = true;
if (server.args() > 0)
{
all = false;
for (int i = 0; i < server.args(); i++)
{
argReq[server.argName(i).c_str()] = true;
}
}

// Build the JSON string
// Note that newlines and indentation are not required within the json,
// but are included to improve readability in the console log.
std::string json = "{\n"; // open the json
if (all || argReq["uptime"])
json.append(std::string(" \"upTime\": ") + std::to_string(millis()) + ",\n");
if (all)
json.append(std::string(" \"deviceName\": \"") + device_name + "\",\n");
if (all)
json.append(std::string(" \"paired\": ") + (homekit_is_paired() ? "true" : "false") + ",\n");
if (all)
json.append(std::string(" \"firmwareVersion\": \"") + std::string(AUTO_VERSION) + "\",\n");
if (all)
json.append(std::string(" \"accessoryID\": \"") + hks->accessory_id + "\",\n");
if (all)
json.append(std::string(" \"localIP\": \"") + WiFi.localIP().toString().c_str() + "\",\n");
if (all)
json.append(std::string(" \"subnetMask\": \"") + WiFi.subnetMask().toString().c_str() + "\",\n");
if (all)
json.append(std::string(" \"gatewayIP\": \"") + WiFi.gatewayIP().toString().c_str() + "\",\n");
if (all)
json.append(std::string(" \"macAddress\": \"") + WiFi.macAddress().c_str() + "\",\n");
if (all)
json.append(std::string(" \"wifiSSID\": \"") + WiFi.SSID().c_str() + "\",\n");
if (all || argReq["doorstate"])
{
std::string doorState = "";
switch (garage_door.current_state)
{
case 0:
doorState = "Open";
break;
case 1:
doorState = "Closed";
break;
case 2:
doorState = "Opening";
break;
case 3:
doorState = "Closing";
break;
case 4:
doorState = "Stopped";
break;
default:
doorState = "Unknown";
}
json.append(std::string(" \"garageDoorState\": \"") + doorState + "\",\n");
}
if (all || argReq["lockstate"])
{
std::string lockState = "";
switch (garage_door.current_lock)
{
case 0:
lockState = "Unsecured";
break;
case 1:
lockState = "Secured";
break;
case 2:
lockState = "Jammed";
break;
default:
lockState = "Unknown";
}
json.append(std::string(" \"garageLockState\": \"") + lockState + "\",\n");
}
if (all || argReq["lighton"])
json.append(std::string(" \"garageLightOn\": ") + (garage_door.light ? "true" : "false") + ",\n");
if (all || argReq["motion"])
json.append(std::string(" \"garageMotion\": ") + (garage_door.motion ? "true" : "false") + ",\n");
if (all || argReq["obstruction"])
json.append(std::string(" \"garageObstructed\": ") + (garage_door.obstructed ? "true" : "false") + ",\n");

// remove the final comma/newline to ensure valid JSON syntax
json.erase(json.rfind(",\n"));
json.append("\n}"); // close the json

// Only log if all requested (no arguments).
// Avoids spaming console log if repeated requests for one value.
if (all)
RINFO("Status requested:\n%s", json.c_str());

server.send(200, "application/json", json.c_str());
return;
}
Binary file added src/www/favicon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 73860af

Please sign in to comment.