Skip to content

Commit

Permalink
Implemented Web API for Smart Home integration
Browse files Browse the repository at this point in the history
+ Update EspAsyncWeb lib
  • Loading branch information
DrA1ex committed Aug 24, 2024
1 parent 00c169e commit e506630
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 4 deletions.
4 changes: 3 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ monitor_filters = esp8266_exception_decoder

lib_deps = fastled/FastLED@^3.6.0
me-no-dev/ESPAsyncTCP@^1.2.2
me-no-dev/ESP Async WebServer@^1.2.3
#me-no-dev/ESP Async WebServer@^1.2.4 TODO: doesn't work well
https://github.com/yubox-node-org/ESPAsyncWebServer.git
arduino-libraries/NTPClient@^3.2.1
bblanchon/ArduinoJson@^7.1.0

[env:debug]
build_type = debug
Expand Down
10 changes: 8 additions & 2 deletions src/constants.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
#pragma once

#include "misc/led.h"

#include "credentials.h"
#include "sys_constants.h"

#define WIFI_MODE (WIFI_AP_MODE)
#define WIFI_SSID "ESP_LED"
#define WIFI_PASSWORD "12345678"
#define WIFI_SSID CREDENTIAL_WIFI_SSID
#define WIFI_PASSWORD CREDENTIAL_WIFI_PASSWORD

#define WIFI_CONNECTION_CHECK_INTERVAL (5000u) // Interval (ms) between Wi-Fi connection check
#define WIFI_MAX_CONNECTION_ATTEMPT_INTERVAL (0u) // Max time (ms) to wait for Wi-Fi connection before switch to AP mode
// 0 - Newer switch to AP mode

#define WEB_AUTH
#define AUTH_USER CREDENTIAL_AUTH_USER
#define AUTH_PASSWORD CREDENTIAL_AUTH_PASSWORD

#define TIME_ZONE (5.f) // GMT +5:00

#define MDNS_NAME "esp_lamp"
Expand Down
7 changes: 7 additions & 0 deletions src/credentials.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#define CREDENTIAL_WIFI_SSID "ESP_LED"
#define CREDENTIAL_WIFI_PASSWORD "12345678"

#define CREDENTIAL_AUTH_USER "esp_lamp"
#define CREDENTIAL_AUTH_PASSWORD "password"
7 changes: 7 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "network/wifi.h"
#include "network/web.h"
#include "network/protocol/server/api.h"
#include "network/protocol/server/udp.h"
#include "network/protocol/server/ws.h"

Expand Down Expand Up @@ -80,6 +81,7 @@ Application app(config_storage, preset_names_storage, preset_configs_storage, cu
WifiManager wifi_manager;
WebServer web_server(WEB_PORT);

ApiWebServer api_server(app);
UdpServer udp_server(app);
WebSocketServer ws_server(app);

Expand Down Expand Up @@ -302,6 +304,11 @@ void service_loop(void *) {
break;

case 2:
#ifdef WEB_AUTH
web_server.add_handler(new WebAuthHandler());
#endif

api_server.begin(web_server);
udp_server.begin(UDP_PORT);
ws_server.begin(web_server);

Expand Down
95 changes: 95 additions & 0 deletions src/network/protocol/server/api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "api.h"

#include <ArduinoJson.h>

#include "utils/math.h"


ApiWebServer::ApiWebServer(Application &application, const char *path) : _app(application), _path(path) {}

void ApiWebServer::begin(WebServer &server) {
server.on((_path + String("/power")).c_str(), HTTP_GET, [this](AsyncWebServerRequest *request) {
if (!request->hasArg("value")) {
D_PRINT("Request power status");
return response_with_json(request, JsonPropListT{
{"status", "ok"},
{"value", _app.config.power ? 1 : 0},
});
}

bool enabled = request->arg("value") == "1";
_app.set_power(enabled);

response_with_json_status(request, "ok");
});

server.on((_path + String("/brightness")).c_str(), HTTP_GET, [this](AsyncWebServerRequest *request) {
if (!request->hasArg("value")) {
D_PRINT("Request brightness status");
return response_with_json(request, JsonPropListT{
{"status", "ok"},
{"value", map16(_app.config.max_brightness, 255, 100)},
});
}

auto new_brightness = map16(request->arg("value").toInt(), 100, 255);

_app.config.max_brightness = new_brightness;
_app.load();

response_with_json_status(request, "ok");
});

server.on((_path + String("/color")).c_str(), HTTP_GET, [this](AsyncWebServerRequest *request) {
if (!request->hasArg("value")) {
D_PRINT("Request color status");
auto hsv = _app.preset().color_effect == ColorEffectEnum::SOLID
? CHSV(_app.preset().speed, _app.preset().scale, 255)
: CHSV(255, 255, 225);

CRGB color{};
hsv2rgb_rainbow(hsv, color);

return response_with_json(request, JsonPropListT{
{"status", "ok"},
{"value", static_cast<uint32_t>(color) & 0xffffff},

{"hue", hsv.hue},
{"sat", hsv.sat},
{"bri", hsv.val},
});
}

PresetConfig *preset = nullptr;
if (_app.preset().color_effect == ColorEffectEnum::SOLID) {
preset = &_app.preset();
} else {
for (int i = 0; i < _app.preset_configs.count; ++i) {
if (_app.preset_configs.presets[i].color_effect == ColorEffectEnum::SOLID) {
_app.change_preset(i);
preset = &_app.preset_configs.presets[i];
break;
}
}

if (preset == nullptr) {
return response_with_json_status(request, "error");
}
}


auto new_color = CRGB(request->arg("value").toInt());
auto hsv = rgb2hsv_approximate(new_color);
preset->speed = hsv.hue;
preset->scale = hsv.sat;

_app.load();

response_with_json(request, {
{"status", "ok"},
{"hue", hsv.hue},
{"sat", hsv.sat},
{"bri", hsv.val},
});
});
}
18 changes: 18 additions & 0 deletions src/network/protocol/server/api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "constants.h"
#include "application.h"

#include "network/web.h"

#include "utils/network.h"

class ApiWebServer {
Application &_app;
const char *_path;

public:
ApiWebServer(Application &application, const char *path = "/api");

void begin(WebServer &server);
};
15 changes: 14 additions & 1 deletion src/network/web.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,17 @@ void WebServer::begin(FS *fs) {

D_WRITE("Web server listening on port: ");
D_PRINT(_port);
}
}

void WebAuthHandler::handleRequest(AsyncWebServerRequest *request) {
D_PRINTF("Reject request from: %s\n", request->client()->remoteIP().toString().c_str());

request->redirect("https://google.com");
}

bool WebAuthHandler::canHandle(AsyncWebServerRequest *request) {
bool is_local = (request->client()->getRemoteAddress() & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xc0a80000UL);
bool auth_required = _allow_local ? !is_local : true;

return auth_required && !request->authenticate(AUTH_USER, AUTH_PASSWORD);
}
14 changes: 14 additions & 0 deletions src/network/web.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include "ESPAsyncWebServer.h"
#include "FS.h"

#include "constants.h"
#include "debug.h"

class WebServer {
uint16_t _port;
AsyncWebServer _server;
Expand All @@ -19,4 +22,15 @@ class WebServer {
}

inline void add_handler(AsyncWebHandler *handler) { _server.addHandler(handler); }
};

class WebAuthHandler : public AsyncWebHandler {
private:
bool _allow_local = true;

public:
inline void set_allow_local(bool value) { _allow_local = value; }

virtual bool canHandle(AsyncWebServerRequest *request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final;
};
9 changes: 9 additions & 0 deletions src/utils/math.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <cstdint>
#include <cmath>

inline uint16_t map16(uint16_t value, uint16_t limit_src, uint16_t limit_dst) {
value = std::max((uint16_t) 0, std::min(limit_src, value));
return (int32_t) value * limit_dst / limit_src;
}
32 changes: 32 additions & 0 deletions src/utils/network.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <variant>

#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>

typedef std::variant<bool, int, long, long long, float, double, const char *> JsonPropVariantT;
typedef std::initializer_list<std::pair<const char *, JsonPropVariantT>> JsonPropListT;

inline void response_with_json(AsyncWebServerRequest *request, JsonDocument &doc) {
auto *response = request->beginResponseStream("application/json");
serializeJson(doc, *response);
request->send(response);
}


inline void response_with_json(AsyncWebServerRequest *request, JsonPropListT props) {
JsonDocument doc;
for (auto prop: props) {
std::visit([&](auto &&arg) { doc[prop.first] = arg; }, prop.second);
}

response_with_json(request, doc);
}

inline void response_with_json_status(AsyncWebServerRequest *request, const char *status) {
JsonDocument doc;
doc["status"] = status;

response_with_json(request, doc);
}

0 comments on commit e506630

Please sign in to comment.