#include "sh_core_esp8266.h"
// ---------------------- weak device hooks (можно переопределить в прошивке) ----------------------
__attribute__((weak)) void appendStatusJsonFields(String &json) { (void)json; }
__attribute__((weak)) void appendAboutJsonFields(String &json) { (void)json; }
__attribute__((weak)) bool deviceHandleAction(const String &action,
const String ¶msJson,
String &errorCode,
String &errorMessage)
{
(void)action;
(void)paramsJson;
errorCode = "IllegalActionOrParams";
errorMessage = "Action not implemented";
return false;
}
__attribute__((weak)) void deviceHandleReset() {}
// ---------------------- helpers ----------------------
static String deviceModeToString(DeviceMode m) {
switch (m) {
case DEVICE_MODE_SETUP: return F("setup");
case DEVICE_MODE_NORMAL: return F("normal");
case DEVICE_MODE_ERROR: return F("error");
case DEVICE_MODE_UPDATING: return F("updating");
}
return F("error");
}
static void sendJson(int code, const String &body) {
server.send(code, F("application/json; charset=utf-8"), body);
}
static String extractJsonStringValue(const String &body, const String &key) {
String pattern = "\"" + key + "\"";
int keyIndex = body.indexOf(pattern);
if (keyIndex < 0) return "";
int colonIndex = body.indexOf(':', keyIndex);
if (colonIndex < 0) return "";
int firstQuote = body.indexOf('"', colonIndex + 1);
if (firstQuote < 0) return "";
int secondQuote = body.indexOf('"', firstQuote + 1);
if (secondQuote < 0) return "";
return body.substring(firstQuote + 1, secondQuote);
}
static String extractJsonObject(const String &body, const String &key) {
String pattern = "\"" + key + "\"";
int keyIndex = body.indexOf(pattern);
if (keyIndex < 0) return "";
int colonIndex = body.indexOf(':', keyIndex);
if (colonIndex < 0) return "";
int braceIndex = body.indexOf('{', colonIndex);
if (braceIndex < 0) return "";
int depth = 0;
for (int i = braceIndex; i < (int)body.length(); i++) {
char c = body[i];
if (c == '{') depth++;
else if (c == '}') {
depth--;
if (depth == 0) {
return body.substring(braceIndex, i + 1);
}
}
}
return "";
}
// ---------------------- auth ----------------------
static bool hasValidToken() {
if (authToken.length() == 0) return false;
String header = server.header(F("Authorization"));
if (!header.startsWith(F("Bearer "))) return false;
String token = header.substring(7);
token.trim();
if (token != authToken) return false;
if (serverBaseUrl.length() > 0) {
IPAddress remote = server.client().remoteIP();
String ipStr = remote.toString();
if (ipStr != serverBaseUrl) {
Serial.print(F("Auth IP mismatch: got "));
Serial.print(ipStr);
Serial.print(F(", expected "));
Serial.println(serverBaseUrl);
return false;
}
}
return true;
}
static bool requireAuth() {
if (!hasValidToken()) {
String json = F("{\"status\":\"error\",\"error\":\"Unauthorized\",\"message\":\"Missing or invalid token\"}");
sendJson(401, json);
return false;
}
return true;
}
static void sendNotAvailable() {
String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Setup mode is not active\"}");
sendJson(403, json);
}
// ---------------------- handlers ----------------------
static void handleAbout() {
IPAddress ip = WiFi.localIP();
if(deviceMode == DEVICE_MODE_SETUP) {
serverBaseUrl = "0.0.0.0";
}
String json = "{";
json += "\"device_name\":\"" + deviceName + "\",";
json += "\"device_type\":\"" + String(DEVICE_TYPE) + "\",";
json += "\"firmware_version\":\"" + String(FW_VERSION) + "\",";
json += "\"device_id\":\"" + getUniqueID() + "\",";
json += "\"server\":\"" + serverBaseUrl + "\",";
json += "\"status\":\"" + deviceModeToString(deviceMode) + "\",";
json += "\"ip_address\":\"" + ip.toString() + "\",";
json += "\"mac_address\":\"" + getMAC() + "\",";
json += "\"uptime\":" + String(millis() / 1000);
appendAboutJsonFields(json);
json += "}";
sendJson(200, json);
}
static void handleStatus() {
if (deviceMode == DEVICE_MODE_NORMAL) {
if (!requireAuth()) return;
}
String json = "{";
json += "\"status\":\"ok\"";
appendStatusJsonFields(json);
json += "}";
sendJson(200, json);
}
static void handleSetDeviceName() {
if (deviceMode != DEVICE_MODE_NORMAL) {
String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Not in normal mode\"}");
sendJson(400, json);
return;
}
if (!requireAuth()) return;
String body = server.arg("plain");
body.trim();
String newName = extractJsonStringValue(body, F("device_name"));
newName.trim();
if (newName.length() == 0) {
String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"device_name is required\"}");
sendJson(400, json);
return;
}
String sanitized;
for (uint16_t i = 0; i < newName.length() && sanitized.length() < DEVICE_NAME_MAX_LEN - 1; i++) {
char c = newName[i];
if (c >= 32 && c != '\"') sanitized += c;
}
if (sanitized.length() == 0) {
String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Invalid device_name\"}");
sendJson(400, json);
return;
}
deviceName = sanitized;
saveDeviceConfig();
String json = F("{\"status\":\"ok\",\"message\":\"Device name updated\"}");
sendJson(200, json);
}
static void handleAction() {
if (deviceMode != DEVICE_MODE_NORMAL) {
String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Action not available in this mode\"}");
sendJson(403, json);
return;
}
if (!requireAuth()) return;
String body = server.arg("plain");
body.trim();
String action = extractJsonStringValue(body, F("action"));
if (action.length() == 0) {
String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Action is required\"}");
sendJson(400, json);
return;
}
String paramsJson = extractJsonObject(body, F("params"));
if (paramsJson.length() == 0) paramsJson = body;
String errorCode;
String errorMessage;
bool ok = deviceHandleAction(action, paramsJson, errorCode, errorMessage);
if (!ok) {
if (errorCode.length() == 0) errorCode = "IllegalActionOrParams";
if (errorMessage.length() == 0) errorMessage = "Device action failed";
String json = "{\"status\":\"error\",\"error\":\"" + errorCode +
"\",\"message\":\"" + errorMessage + "\"}";
sendJson(400, json);
return;
}
String json = F("{\"status\":\"ok\",\"message\":\"Action executed\"}");
sendJson(200, json);
}
static void handleSetToken() {
String body = server.arg("plain");
body.trim();
String newToken = extractJsonStringValue(body, F("token"));
if (newToken.length() == 0) {
String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Token is required\"}");
sendJson(400, json);
return;
}
if (deviceMode == DEVICE_MODE_SETUP) {
IPAddress remote = server.client().remoteIP();
serverBaseUrl = remote.toString();
authToken = newToken;
deviceMode = DEVICE_MODE_NORMAL;
saveDeviceConfig();
String json = F("{\"status\":\"ok\",\"message\":\"Token set. Device mode: normal\"}");
sendJson(200, json);
return;
}
if (deviceMode == DEVICE_MODE_NORMAL) {
if (!requireAuth()) return;
authToken = newToken;
saveDeviceConfig();
String json = F("{\"status\":\"ok\",\"message\":\"Token updated\"}");
sendJson(200, json);
return;
}
String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Cannot set token in current mode\"}");
sendJson(403, json);
}
static void handleRebootApi() {
if (deviceMode != DEVICE_MODE_NORMAL) {
String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Reboot available only in normal mode\"}");
sendJson(403, json);
return;
}
if (!requireAuth()) return;
String json = F("{\"status\":\"ok\",\"message\":\"Device will reboot now\"}");
sendJson(200, json);
Serial.println(F("Reboot requested via REST API"));
delay(500);
ESP.restart();
}
static void handleResetApi() {
if (deviceMode != DEVICE_MODE_NORMAL) {
String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Reset available only in normal mode\"}");
sendJson(403, json);
return;
}
if (!requireAuth()) return;
savedSSID = "";
savedPASS = "";
saveWiFiConfig(savedSSID, savedPASS);
authToken = "";
serverBaseUrl = "";
deviceMode = DEVICE_MODE_SETUP;
saveDeviceConfig();
String json = F("{\"status\":\"ok\",\"message\":\"Device reset to factory settings. Entering setup mode.\"}");
sendJson(200, json);
delay(800);
ESP.restart();
}
// ---------------------- routes register ----------------------
void registerRestApiRoutes() {
server.on(F("/about"), HTTP_GET, handleAbout);
server.on(F("/status"), HTTP_GET, handleStatus);
server.on(F("/action"), HTTP_POST, handleAction);
server.on(F("/set_token"), HTTP_POST, handleSetToken);
server.on(F("/set_device_name"), HTTP_POST, handleSetDeviceName);
server.on(F("/reboot"), HTTP_POST, handleRebootApi);
server.on(F("/reset"), HTTP_POST, handleResetApi);
}