diff --git a/code/espurna/api.cpp b/code/espurna/api.cpp index 7bf76a9f..08f60d7e 100644 --- a/code/espurna/api.cpp +++ b/code/espurna/api.cpp @@ -21,19 +21,8 @@ Copyright (C) 2016-2019 by Xose PĂ©rez #include #include -struct web_api_t { - explicit web_api_t(const String& key, api_get_callback_f getFn, api_put_callback_f putFn) : - key(key), - getFn(getFn), - putFn(putFn) - {} - web_api_t() = delete; - - const String key; - api_get_callback_f getFn; - api_put_callback_f putFn; -}; -std::vector _apis; +constexpr size_t ApiPathSizeMax { 64ul }; +std::vector _apis; // ----------------------------------------------------------------------------- // API @@ -50,16 +39,10 @@ bool _asJson(AsyncWebServerRequest *request) { void _onAPIsText(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("text/plain"); - String output; - output.reserve(48); + char buffer[ApiPathSizeMax] = {0}; for (auto& api : _apis) { - output = ""; - output += api.key; - output += " -> "; - output += "/api/"; - output += api.key; - output += '\n'; - response->write(output.c_str()); + sprintf_P(buffer, PSTR("/api/%s\n"), api.path.c_str()); + response->write(buffer); } request->send(response); } @@ -69,19 +52,14 @@ constexpr size_t ApiJsonBufferSize = 1024; void _onAPIsJson(AsyncWebServerRequest *request) { DynamicJsonBuffer jsonBuffer(ApiJsonBufferSize); - JsonObject& root = jsonBuffer.createObject(); + JsonArray& root = jsonBuffer.createArray(); - constexpr const int BUFFER_SIZE = 48; - - for (unsigned int i=0; i < _apis.size(); i++) { - char buffer[BUFFER_SIZE] = {0}; - int res = snprintf(buffer, sizeof(buffer), "/api/%s", _apis[i].key.c_str()); - if ((res < 0) || (res > (BUFFER_SIZE - 1))) { - request->send(500); - return; - } - root[_apis[i].key] = buffer; + char buffer[ApiPathSizeMax] = {0}; + for (auto& api : _apis) { + sprintf(buffer, "/api/%s", api.path.c_str()); + root.add(buffer); } + AsyncResponseStream *response = request->beginResponseStream("application/json"); root.printTo(*response); request->send(response); @@ -129,87 +107,159 @@ void _onRPC(AsyncWebServerRequest *request) { } -bool _apiRequestCallback(AsyncWebServerRequest *request) { +struct ApiMatch { + Api* api { nullptr }; + Api::Type type { Api::Type::Basic }; +}; - String url = request->url(); +ApiMatch _apiMatch(const String& url, AsyncWebServerRequest* request) { - // Main API entry point - if (url.equals("/api") || url.equals("/apis")) { - _onAPIs(request); - return true; + ApiMatch result; + char buffer[ApiPathSizeMax] = {0}; + + for (auto& api : _apis) { + sprintf_P(buffer, PSTR("/api/%s"), api.path.c_str()); + if (url != buffer) { + continue; + } + + auto type = _asJson(request) + ? Api::Type::Json + : Api::Type::Basic; + + result.api = &api; + result.type = type; + break; } - // Main RPC entry point - if (url.equals("/rpc")) { - _onRPC(request); + return result; +} + +bool _apiDispatchRequest(const String& url, AsyncWebServerRequest* request) { + + auto match = _apiMatch(url, request); + if (!match.api) { + return false; + } + + if (match.type != match.api->type) { + DEBUG_MSG_P(PSTR("[API] Cannot handle the request type\n")); + request->send(404); return true; } - // Not API request - if (!url.startsWith("/api/")) return false; + const bool is_put = ( + (!apiRestFul() || (request->method() == HTTP_PUT)) + && request->hasParam("value", request->method() == HTTP_PUT) + ); - for (auto& api : _apis) { + ApiBuffer buffer; - // Search API url for the exact match - if (!url.endsWith(api.key)) continue; + switch (match.api->type) { - // Log and check credentials - webLog(request); - if (!apiAuthenticate(request)) return false; + case Api::Type::Basic: { + if (!match.api->get.basic) { + break; + } - // Check if its a PUT - if (api.putFn != NULL) { - if (!apiRestFul() || (request->method() == HTTP_PUT)) { - if (request->hasParam("value", request->method() == HTTP_PUT)) { - AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT); - (api.putFn)((p->value()).c_str()); - } + if (is_put) { + if (!match.api->put.basic) { + break; } + auto value = request->getParam("value", request->method() == HTTP_PUT)->value(); + //memcpy(buffer.data, value.c_str(), value.length()); + std::copy(value.c_str(), value.c_str() + value.length(), buffer.data); + match.api->get.basic(*match.api, buffer); + buffer.erase(); } - // Get response from callback - char value[API_BUFFER_SIZE] = {0}; - (api.getFn)(value, API_BUFFER_SIZE); + match.api->get.basic(*match.api, buffer); + request->send(200, "text/plain", buffer.data); - // The response will be a 404 NOT FOUND if the resource is not available - if (0 == value[0]) { - DEBUG_MSG_P(PSTR("[API] Sending 404 response\n")); - request->send(404); - return false; + return true; + } + + // TODO: pass the body instead of `value` param + // TODO: handle HTTP_PUT + case Api::Type::Json: { + if (!match.api->get.json || is_put) { + break; } - DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), value); + DynamicJsonBuffer jsonBuffer(API_BUFFER_SIZE); + JsonObject& root = jsonBuffer.createObject(); - // Format response according to the Accept header - if (_asJson(request)) { - char buffer[64]; - if (isNumber(value)) { - snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key.c_str(), value); - } else { - snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": \"%s\" }"), api.key.c_str(), value); - } - request->send(200, "application/json", buffer); - } else { - request->send(200, "text/plain", value); - } + match.api->get.json(*match.api, root); + + AsyncResponseStream *response = request->beginResponseStream("application/json", root.measureLength() + 1); + root.printTo(*response); + request->send(response); return true; + } } - return false; + DEBUG_MSG_P(PSTR("[API] Method not supported\n")); + request->send(405); + + return true; + +} + +bool _apiRequestCallback(AsyncWebServerRequest* request) { + + String url = request->url(); + + if (url.equals("/rpc")) { + _onRPC(request); + return true; + } + + if (url.equals("/api") || url.equals("/apis")) { + _onAPIs(request); + return true; + } + + if (!url.startsWith("/api/")) return false; + if (!apiAuthenticate(request)) return false; + + return _apiDispatchRequest(url, request); } // ----------------------------------------------------------------------------- -void apiRegister(const String& key, api_get_callback_f getFn, api_put_callback_f putFn) { - _apis.emplace_back(key, std::move(getFn), std::move(putFn)); +void apiReserve(size_t size) { + _apis.reserve(_apis.size() + size); +} + +void apiRegister(const Api& api) { + if (api.path.length() >= (ApiPathSizeMax - strlen("/api/") - 1ul)) { + return; + } + _apis.push_back(api); } void apiSetup() { webRequestRegister(_apiRequestCallback); } +void apiOk(const Api&, ApiBuffer& buffer) { + buffer.data[0] = 'O'; + buffer.data[1] = 'K'; + buffer.data[2] = '\0'; +} + +void apiError(const Api&, ApiBuffer& buffer) { + buffer.data[0] = '-'; + buffer.data[1] = 'E'; + buffer.data[2] = 'R'; + buffer.data[3] = 'R'; + buffer.data[4] = 'O'; + buffer.data[5] = 'R'; + buffer.data[6] = '\0'; +} + #endif // API_SUPPORT diff --git a/code/espurna/api.h b/code/espurna/api.h index 7474b55b..4d9651ee 100644 --- a/code/espurna/api.h +++ b/code/espurna/api.h @@ -22,14 +22,71 @@ String apiKey(); #if WEB_SUPPORT && API_SUPPORT -#include +#include -using api_get_callback_f = std::function; -using api_put_callback_f = std::function ; +constexpr unsigned char ApiUnusedArg = 0u; -void apiRegister(const String& key, api_get_callback_f getFn, api_put_callback_f putFn = nullptr); +struct ApiBuffer { + constexpr static size_t size = API_BUFFER_SIZE; + char data[size]; + + void erase() { + std::fill(data, data + size, '\0'); + } +}; + +struct Api { + using BasicHandler = void(*)(const Api& api, ApiBuffer& buffer); + using JsonHandler = void(*)(const Api& api, JsonObject& root); + + enum class Type { + Basic, + Json + }; + + Api() = delete; + + Api(const String& path_, Type type_, unsigned char arg_, BasicHandler get_, BasicHandler put_ = nullptr) : + path(path_), + type(type_), + arg(arg_) + { + get.basic = get_; + put.basic = put_; + } + + Api(const String& path_, Type type_, unsigned char arg_, JsonHandler get_, JsonHandler put_ = nullptr) : + path(path_), + type(type_), + arg(arg_) + { + get.json = get_; + put.json = put_; + } + + String path; + Type type; + unsigned char arg; + + union { + BasicHandler basic; + JsonHandler json; + } get; + + union { + BasicHandler basic; + JsonHandler json; + } put; +}; + +void apiRegister(const Api& api); void apiCommonSetup(); void apiSetup(); +void apiReserve(size_t); + +void apiError(const Api&, ApiBuffer& buffer); +void apiOk(const Api&, ApiBuffer& buffer); + #endif // API_SUPPORT == 1 diff --git a/code/espurna/light.cpp b/code/espurna/light.cpp index 276ca11d..e7acddb9 100644 --- a/code/espurna/light.cpp +++ b/code/espurna/light.cpp @@ -853,86 +853,101 @@ void lightBroker() { #if API_SUPPORT -void _lightAPISetup() { +void _lightApiSetup() { + + // Note that we expect a fixed number of entries. + // Otherwise, underlying vector will reserve more than we need (likely, *2 of the current size) + apiReserve( + (_light_has_color ? 4u : 0u) + 2u + _light_channels.size() + ); if (_light_has_color) { - apiRegister(MQTT_TOPIC_COLOR_RGB, - [](char * buffer, size_t len) { + apiRegister({ + MQTT_TOPIC_COLOR_RGB, Api::Type::Basic, ApiUnusedArg, + [](const Api&, ApiBuffer& buffer) { if (getSetting("useCSS", 1 == LIGHT_USE_CSS)) { - _toRGB(buffer, len, true); + _toRGB(buffer.data, buffer.size, true); } else { - _toLong(buffer, len, true); + _toLong(buffer.data, buffer.size, true); } }, - [](const char * payload) { - lightColor(payload, true); + [](const Api&, ApiBuffer& buffer) { + lightColor(buffer.data, true); lightUpdate(true, true); } - ); + }); - apiRegister(MQTT_TOPIC_COLOR_HSV, - [](char * buffer, size_t len) { - _toHSV(buffer, len); + apiRegister({ + MQTT_TOPIC_COLOR_HSV, Api::Type::Basic, ApiUnusedArg, + [](const Api&, ApiBuffer& buffer) { + _toHSV(buffer.data, buffer.size); }, - [](const char * payload) { - lightColor(payload, false); - lightUpdate(true, true); - } - ); - - apiRegister(MQTT_TOPIC_KELVIN, - [](char * buffer, size_t len) {}, - [](const char * payload) { - _lightAdjustKelvin(payload); + [](const Api&, ApiBuffer& buffer) { + lightColor(buffer.data, false); lightUpdate(true, true); } - ); + }); - apiRegister(MQTT_TOPIC_MIRED, - [](char * buffer, size_t len) {}, - [](const char * payload) { - _lightAdjustMireds(payload); + apiRegister({ + MQTT_TOPIC_MIRED, Api::Type::Basic, ApiUnusedArg, + [](const Api&, ApiBuffer& buffer) { + sprintf(buffer.data, PSTR("%d"), _light_mireds); + }, + [](const Api&, ApiBuffer& buffer) { + _lightAdjustMireds(buffer.data); lightUpdate(true, true); } - ); - - } + }); - for (unsigned int id=0; id<_light_channels.size(); id++) { - - char key[15]; - snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id); - apiRegister(key, - [id](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("%d"), _light_channels[id].target); + apiRegister({ + MQTT_TOPIC_KELVIN, Api::Type::Basic, ApiUnusedArg, + [](const Api&, ApiBuffer& buffer) { + sprintf(buffer.data, PSTR("%d"), _toKelvin(_light_mireds)); }, - [id](const char * payload) { - _lightAdjustChannel(id, payload); + [](const Api&, ApiBuffer& buffer) { + _lightAdjustKelvin(buffer.data); lightUpdate(true, true); } - ); + }); } - apiRegister(MQTT_TOPIC_TRANSITION, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("%d"), lightTransitionTime()); + apiRegister({ + MQTT_TOPIC_TRANSITION, Api::Type::Basic, ApiUnusedArg, + [](const Api&, ApiBuffer& buffer) { + snprintf_P(buffer.data, buffer.size, PSTR("%u"), lightTransitionTime()); }, - [](const char * payload) { - lightTransitionTime(atol(payload)); + [](const Api&, ApiBuffer& buffer) { + lightTransitionTime(atol(buffer.data)); } - ); + }); - apiRegister(MQTT_TOPIC_BRIGHTNESS, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("%d"), _light_brightness); + apiRegister({ + MQTT_TOPIC_BRIGHTNESS, Api::Type::Basic, ApiUnusedArg, + [](const Api&, ApiBuffer& buffer) { + snprintf_P(buffer.data, buffer.size, PSTR("%u"), _light_brightness); }, - [](const char * payload) { - _lightAdjustBrightness(payload); + [](const Api&, ApiBuffer& buffer) { + _lightAdjustBrightness(buffer.data); lightUpdate(true, true); } - ); + }); + + char path[32] = {0}; + for (unsigned char id = 0; id < _light_channels.size(); ++id) { + snprintf_P(path, sizeof(path), PSTR(MQTT_TOPIC_CHANNEL "/%u"), id); + apiRegister({ + path, Api::Type::Basic, id, + [](const Api& api, ApiBuffer& buffer) { + snprintf_P(buffer.data, buffer.size, PSTR("%u"), _light_channels[api.arg].target); + }, + [](const Api& api, ApiBuffer& buffer) { + _lightAdjustChannel(api.arg, buffer.data); + lightUpdate(true, true); + } + }); + } } @@ -1421,7 +1436,7 @@ void lightSetup() { #endif #if API_SUPPORT - _lightAPISetup(); + _lightApiSetup(); #endif #if MQTT_SUPPORT diff --git a/code/espurna/relay.cpp b/code/espurna/relay.cpp index ab6f1169..7dfadf16 100644 --- a/code/espurna/relay.cpp +++ b/code/espurna/relay.cpp @@ -46,60 +46,50 @@ struct DummyPin final : public BasePin { struct relay_t { - using pin_type = std::unique_ptr; + using pin_type = BasePin; - // Default to empty relay configuration, as we allow switches to exist without real GPIOs + // Share the same dummy pin between different relays, no need to duplicate - relay_t(pin_type&& pin, unsigned char type, pin_type&& reset_pin) : - pin(std::move(pin)), - reset_pin(std::move(reset_pin)), - type(type), - delay_on(0), - delay_off(0), - pulse(RELAY_PULSE_NONE), - pulse_ms(0), - current_status(false), - target_status(false), - lock(RELAY_LOCK_DISABLED), - fw_start(0), - fw_count(0), - change_start(0), - change_delay(0), - report(false), - group_report(false) - {} + static pin_type* DummyPinInstance; + + // Default to empty relay configuration, as we allow switches to exist without real GPIOs - relay_t() : - relay_t(std::make_unique(GPIO_NONE), RELAY_TYPE_NORMAL, std::make_unique(GPIO_NONE)) + relay_t() = default; + relay_t(pin_type* pin_, unsigned char type_, pin_type* reset_pin_) : + pin(pin_), + reset_pin(reset_pin_), + type(type_) {} - pin_type pin; // GPIO pin for the relay - pin_type reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED + pin_type* pin { DummyPinInstance }; // GPIO pin for the relay + pin_type* reset_pin { DummyPinInstance }; // GPIO to reset the relay if RELAY_TYPE_LATCHED - unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE - unsigned long delay_on; // Delay to turn relay ON - unsigned long delay_off; // Delay to turn relay OFF - unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON - unsigned long pulse_ms; // Pulse length in millis + unsigned char type { RELAY_TYPE_NORMAL }; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE + unsigned long delay_on { 0ul }; // Delay to turn relay ON + unsigned long delay_off { 0ul }; // Delay to turn relay OFF + unsigned char pulse { RELAY_PULSE_NONE }; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON + unsigned long pulse_ms { 0ul }; // Pulse length in millis // Status variables - bool current_status; // Holds the current (physical) status of the relay - bool target_status; // Holds the target status - unsigned char lock; // Holds the value of target status, that cannot be changed afterwards. (0 for false, 1 for true, 2 to disable) - unsigned long fw_start; // Flood window start time - unsigned char fw_count; // Number of changes within the current flood window - unsigned long change_start; // Time when relay was scheduled to change - unsigned long change_delay; // Delay until the next change - bool report; // Whether to report to own topic - bool group_report; // Whether to report to group topic + bool current_status { false }; // Holds the current (physical) status of the relay + bool target_status { false }; // Holds the target status + unsigned char lock { RELAY_LOCK_DISABLED }; // Holds the value of target status, that cannot be changed afterwards. (0 for false, 1 for true, 2 to disable) + unsigned long fw_start { 0ul }; // Flood window start time + unsigned char fw_count { 0u }; // Number of changes within the current flood window + unsigned long change_start { 0ul }; // Time when relay was scheduled to change + unsigned long change_delay { 0ul }; // Delay until the next change + bool report { false }; // Whether to report to own topic + bool group_report { false }; // Whether to report to group topic - // Helping objects + // Helper objects - Ticker pulseTicker; // Holds the pulse back timer + Ticker pulseTicker; // Holds the pulse back timer }; +BasePin* relay_t::DummyPinInstance = new DummyPin(GPIO_NONE); + std::vector _relays; bool _relayRecursive = false; size_t _relayDummy = 0; @@ -1032,60 +1022,72 @@ void relaySetupWS() { void relaySetupAPI() { - char key[20]; + // Note that we expect a fixed number of entries. + // Otherwise, underlying vector will reserve more than we need (likely, *2 of the current size) + apiReserve(2u + (relayCount() * 2u)); - // API entry points (protected with apikey) - for (unsigned int relayID=0; relayID(_relayPin(id)), + new gpio_type(pin), _relayType(id), - std::make_unique(_relayResetPin(id)) + new gpio_type(_relayResetPin(id)) ); } diff --git a/code/espurna/rfbridge.cpp b/code/espurna/rfbridge.cpp index f87d2690..cc691b43 100644 --- a/code/espurna/rfbridge.cpp +++ b/code/espurna/rfbridge.cpp @@ -55,7 +55,13 @@ unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0}; unsigned char _uartpos = 0; unsigned char _learnId = 0; -bool _learnStatus = true; +enum class RfbLearn { + Disabled, + On, + Off +}; + +RfbLearn _learnStatus = RfbLearn::Disabled; bool _rfbin = false; struct rfb_message_t { @@ -66,7 +72,6 @@ static std::queue _rfb_message_queue; #if RFB_DIRECT RCSwitch * _rfModem; - bool _learning = false; #endif bool _rfb_receive = false; @@ -118,7 +123,7 @@ bool _rfbWebSocketOnKeyCheck(const char * key, JsonVariant& value) { } void _rfbWebSocketOnData(JsonObject& root) { - _rfbWebSocketSendCodeArray(root, 0, relayCount()); + _rfbWebSocketSendCodeArray(root, 0, relayCount()); } #endif // WEB_SUPPORT @@ -190,10 +195,10 @@ void _rfbDecode() { } - if (action == RF_CODE_LEARN_OK) { + if ((action == RF_CODE_LEARN_OK) && (_learnStatus != RfbLearn::Disabled)) { DEBUG_MSG_P(PSTR("[RF] Learn success\n")); - rfbStore(_learnId, _learnStatus, buffer); + rfbStore(_learnId, (_learnStatus == RfbLearn::On), buffer); // Websocket update #if WEB_SUPPORT @@ -213,7 +218,7 @@ void _rfbDecode() { unsigned char id; unsigned char status; bool matched = _rfbMatch(buffer, id, status, buffer); - + if (matched) { DEBUG_MSG_P(PSTR("[RF] Matched message '%s'\n"), buffer); _rfbin = true; @@ -320,7 +325,6 @@ void _rfbAckImpl() {} void _rfbLearnImpl() { DEBUG_MSG_P(PSTR("[RF] Entering LEARN mode\n")); - _learning = true; } void _rfbSendImpl(uint8_t * message) { @@ -351,10 +355,10 @@ void _rfbReceiveImpl() { if (!_rfb_receive) return; static long learn_start = 0; - if (!_learning && learn_start) { + if ((_learnStatus == RfbLearn::Disabled) && learn_start) { learn_start = 0; } - if (_learning) { + if (_learnStatus != RfbLearn::Disabled) { if (!learn_start) { DEBUG_MSG_P(PSTR("[RF] Arming learn timeout\n")); learn_start = millis(); @@ -364,7 +368,7 @@ void _rfbReceiveImpl() { memset(_uartbuf, 0, sizeof(_uartbuf)); _uartbuf[0] = RF_CODE_LEARN_KO; _rfbDecode(); - _learning = false; + _learnStatus = RfbLearn::Disabled; } } @@ -378,7 +382,7 @@ void _rfbReceiveImpl() { unsigned int timing = _rfModem->getReceivedDelay(); memset(_uartbuf, 0, sizeof(_uartbuf)); unsigned char *msgbuf = _uartbuf + 1; - _uartbuf[0] = _learning ? RF_CODE_LEARN_OK: RF_CODE_RFIN; + _uartbuf[0] = (_learnStatus != RfbLearn::Disabled) ? RF_CODE_LEARN_OK: RF_CODE_RFIN; msgbuf[0] = 0xC0; msgbuf[1] = _rfModem->getReceivedProtocol(); msgbuf[2] = timing >> 8; @@ -389,7 +393,7 @@ void _rfbReceiveImpl() { msgbuf[7] = rf_code >> 8; msgbuf[8] = rf_code >> 0; _rfbDecode(); - _learning = false; + _learnStatus = RfbLearn::Disabled; } } _rfModem->resetAvailable(); @@ -482,6 +486,37 @@ void _rfbParseCode(char * code) { } +void _rfbLearnFromPayload(const char* payload) { + // The payload must be the `relayID,mode` (where mode is either 0 or 1) + const char* sep = strchr(payload, ','); + if (NULL == sep) { + return; + } + + // ref. RelaysMax, we only have up to 2 digits + char relay[3] {0, 0, 0}; + if ((sep - payload) > 2) { + return; + } + + std::copy(payload, sep, relay); + if (!isNumber(relay)) { + return; + } + + _learnId = atoi(relay); + if (_learnId >= relayCount()) { + DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId); + return; + } + + ++sep; + if ((*sep == '0') || (*sep == '1')) { + _learnStatus = (*sep != '0') ? RfbLearn::On : RfbLearn::Off; + _rfbLearnImpl(); + } +} + #if MQTT_SUPPORT void _rfbMqttCallback(unsigned int type, const char * topic, char * payload) { @@ -504,30 +539,22 @@ void _rfbMqttCallback(unsigned int type, const char * topic, char * payload) { if (type == MQTT_MESSAGE_EVENT) { - // Match topic String t = mqttMagnitude((char *) topic); - // Check if should go into learn mode if (t.startsWith(MQTT_TOPIC_RFLEARN)) { - - _learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt(); - if (_learnId >= relayCount()) { - DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId); - return; - } - _learnStatus = (char)payload[0] != '0'; - _rfbLearnImpl(); + _rfbLearnFromPayload(payload); return; - } if (t.equals(MQTT_TOPIC_RFOUT)) { _rfbParseCode(payload); + return; } #if !RFB_DIRECT if (t.equals(MQTT_TOPIC_RFRAW)) { _rfbParseRaw(payload); + return; } #endif @@ -539,47 +566,42 @@ void _rfbMqttCallback(unsigned int type, const char * topic, char * payload) { #if API_SUPPORT -void _rfbAPISetup() { +void _rfbApiSetup() { - apiRegister(MQTT_TOPIC_RFOUT, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("OK")); - }, - [](const char * payload) { - _rfbParseCode((char *) payload); + apiReserve(3u); + + apiRegister({ + MQTT_TOPIC_RFOUT, Api::Type::Basic, ApiUnusedArg, + apiOk, // just a stub, nothing to return + [](const Api&, ApiBuffer& buffer) { + _rfbParseCode(buffer.data); } - ); + }); - apiRegister(MQTT_TOPIC_RFLEARN, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("OK")); - }, - [](const char * payload) { - // The payload must be the relayID plus the mode (0 or 1) - char * tok = strtok((char *) payload, ","); - if (NULL == tok) return; - if (!isNumber(tok)) return; - _learnId = atoi(tok); - if (_learnId >= relayCount()) { - DEBUG_MSG_P(PSTR("[RF] Wrong learnID (%d)\n"), _learnId); - return; + apiRegister({ + MQTT_TOPIC_RFLEARN, Api::Type::Basic, ApiUnusedArg, + [](const Api&, ApiBuffer& buffer) { + if (_learnStatus == RfbLearn::Disabled) { + snprintf_P(buffer.data, buffer.size, PSTR("waiting")); + } else { + snprintf_P(buffer.data, buffer.size, PSTR("id:%u,status:%c"), + _learnId, (_learnStatus == RfbLearn::On) ? 'y' : 'n' + ); } - tok = strtok(NULL, ","); - if (NULL == tok) return; - _learnStatus = (char) tok[0] != '0'; - _rfbLearnImpl(); + }, + [](const Api&, ApiBuffer& buffer) { + _rfbLearnFromPayload(buffer.data); } - ); + }); #if !RFB_DIRECT - apiRegister(MQTT_TOPIC_RFRAW, - [](char * buffer, size_t len) { - snprintf_P(buffer, len, PSTR("OK")); - }, - [](const char * payload) { - _rfbParseRaw((char *)payload); + apiRegister({ + MQTT_TOPIC_RFRAW, Api::Type::Basic, ApiUnusedArg, + apiOk, // just a stub, nothing to return + [](const Api&, ApiBuffer& buffer) { + _rfbParseRaw(buffer.data); } - ); + }); #endif } @@ -596,7 +618,7 @@ void _rfbInitCommands() { terminalError(F("Wrong arguments")); return; } - + // 1st argument is relayID int id = ctx.argv[1].toInt(); if (id >= relayCount()) { @@ -617,7 +639,7 @@ void _rfbInitCommands() { terminalError(F("Wrong arguments")); return; } - + // 1st argument is relayID int id = ctx.argv[1].toInt(); if (id >= relayCount()) { @@ -693,7 +715,7 @@ void rfbStatus(unsigned char id, bool status) { void rfbLearn(unsigned char id, bool status) { _learnId = id; - _learnStatus = status; + _learnStatus = status ? RfbLearn::On : RfbLearn::Off; _rfbLearnImpl(); } @@ -723,7 +745,7 @@ void rfbSetup() { #endif #if API_SUPPORT - _rfbAPISetup(); + _rfbApiSetup(); #endif #if WEB_SUPPORT diff --git a/code/espurna/sensor.cpp b/code/espurna/sensor.cpp index 7b044769..7da76c18 100644 --- a/code/espurna/sensor.cpp +++ b/code/espurna/sensor.cpp @@ -1403,27 +1403,50 @@ void _sensorWebSocketOnConnected(JsonObject& root) { #if API_SUPPORT -void _sensorAPISetup() { +String _sensorApiMagnitudeName(sensor_magnitude_t& magnitude) { + String name = magnitudeTopic(magnitude.type); + if (SENSOR_USE_INDEX || (sensor_magnitude_t::counts(magnitude.type) > 1)) name = name + "/" + String(magnitude.index_global); + return name; +} + +void _sensorApiJsonCallback(const Api&, JsonObject& root) { + JsonArray& magnitudes = root.createNestedArray("magnitudes"); for (auto& magnitude : _magnitudes) { + JsonArray& data = magnitudes.createNestedArray(); + data.add(_sensorApiMagnitudeName(magnitude)); + data.add(magnitude.last); + data.add(magnitude.reported); + } +} - String topic = magnitudeTopic(magnitude.type); - if (SENSOR_USE_INDEX || (sensor_magnitude_t::counts(magnitude.type) > 1)) topic = topic + "/" + String(magnitude.index_global); +void _sensorApiGetValue(const Api& api, ApiBuffer& buffer) { + auto& magnitude = _magnitudes[api.arg]; + double value = _sensor_realtime ? magnitude.last : magnitude.reported; + dtostrf(value, 1, magnitude.decimals, buffer.data); +} - api_get_callback_f get_cb = [&magnitude](char * buffer, size_t len) { - double value = _sensor_realtime ? magnitude.last : magnitude.reported; - dtostrf(value, 1, magnitude.decimals, buffer); - }; - api_put_callback_f put_cb = nullptr; +void _sensorApiResetEnergyPutCallback(const Api& api, ApiBuffer& buffer) { + _sensorApiResetEnergy(_magnitudes[api.arg], buffer.data); +} - if (magnitude.type == MAGNITUDE_ENERGY) { - put_cb = [&magnitude](const char* payload) { - _sensorApiResetEnergy(magnitude, payload); - }; - } +void _sensorApiSetup() { - apiRegister(topic.c_str(), get_cb, put_cb); + apiReserve( + _magnitudes.size() + sensor_magnitude_t::counts(MAGNITUDE_ENERGY) + 1u + ); + + apiRegister({"magnitudes", Api::Type::Json, ApiUnusedArg, _sensorApiJsonCallback}); + for (unsigned char id = 0; id < _magnitudes.size(); ++id) { + apiRegister({ + _sensorApiMagnitudeName(_magnitudes[id]).c_str(), + Api::Type::Basic, id, + _sensorApiGetValue, + (_magnitudes[id].type == MAGNITUDE_ENERGY) + ? _sensorApiResetEnergyPutCallback + : nullptr + }); } } @@ -2642,7 +2665,7 @@ void sensorSetup() { // API #if API_SUPPORT - _sensorAPISetup(); + _sensorApiSetup(); #endif // Terminal