Browse Source

Merge pull request #2321 from mcspr/api/no-std-func

- share relay dummypin instance between relay objects, replace unique_ptr with basic pointer (as we never destroy things, everything is brought up on boot and is essentially static)
- reduce overall size of the Api (web_api_t) structure, store a single required unsigned char id inside of the Api object itself.
- drop std::function for the same reason, current implementation only needs a single u8 ID in all cases
- tweak internal functions to expect a certain path size, drop strange comparisons with sprintf return value
- (kind of a hack) add manual calls to vector<Api>::reserve() as we go over the current capacity and vector increases it's size times 2. e.g. for 9 relays, we would allocate space for 32 Api objects as vector size goes from 16 to 32, after we add 18 Api objects with relay + pulse
- (breaking) json calls on a separate path, don't waste time encoding a single entity as json object when we can encode more things
- rfbridge API learn will return the current status instead of a plain OK
mcspr-patch-1
Max Prokhorov 4 years ago
committed by GitHub
parent
commit
6f98861ce1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 464 additions and 295 deletions
  1. +131
    -81
      code/espurna/api.cpp
  2. +61
    -4
      code/espurna/api.h
  3. +67
    -52
      code/espurna/light.cpp
  4. +85
    -83
      code/espurna/relay.cpp
  5. +82
    -60
      code/espurna/rfbridge.cpp
  6. +38
    -15
      code/espurna/sensor.cpp

+ 131
- 81
code/espurna/api.cpp View File

@ -21,19 +21,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
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<web_api_t> _apis;
constexpr size_t ApiPathSizeMax { 64ul };
std::vector<Api> _apis;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// API // API
@ -50,16 +39,10 @@ bool _asJson(AsyncWebServerRequest *request) {
void _onAPIsText(AsyncWebServerRequest *request) { void _onAPIsText(AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("text/plain"); AsyncResponseStream *response = request->beginResponseStream("text/plain");
String output;
output.reserve(48);
char buffer[ApiPathSizeMax] = {0};
for (auto& api : _apis) { 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); request->send(response);
} }
@ -69,19 +52,14 @@ constexpr size_t ApiJsonBufferSize = 1024;
void _onAPIsJson(AsyncWebServerRequest *request) { void _onAPIsJson(AsyncWebServerRequest *request) {
DynamicJsonBuffer jsonBuffer(ApiJsonBufferSize); 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"); AsyncResponseStream *response = request->beginResponseStream("application/json");
root.printTo(*response); root.printTo(*response);
request->send(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; 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 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() { void apiSetup() {
webRequestRegister(_apiRequestCallback); 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 #endif // API_SUPPORT

+ 61
- 4
code/espurna/api.h View File

@ -22,14 +22,71 @@ String apiKey();
#if WEB_SUPPORT && API_SUPPORT #if WEB_SUPPORT && API_SUPPORT
#include <functional>
#include <vector>
using api_get_callback_f = std::function<void(char * buffer, size_t size)>;
using api_put_callback_f = std::function<void(const char * payload)> ;
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 apiCommonSetup();
void apiSetup(); void apiSetup();
void apiReserve(size_t);
void apiError(const Api&, ApiBuffer& buffer);
void apiOk(const Api&, ApiBuffer& buffer);
#endif // API_SUPPORT == 1 #endif // API_SUPPORT == 1

+ 67
- 52
code/espurna/light.cpp View File

@ -853,86 +853,101 @@ void lightBroker() {
#if API_SUPPORT #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) { 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)) { if (getSetting("useCSS", 1 == LIGHT_USE_CSS)) {
_toRGB(buffer, len, true);
_toRGB(buffer.data, buffer.size, true);
} else { } 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); 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); 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); 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); 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); 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 #endif
#if API_SUPPORT #if API_SUPPORT
_lightAPISetup();
_lightApiSetup();
#endif #endif
#if MQTT_SUPPORT #if MQTT_SUPPORT


+ 85
- 83
code/espurna/relay.cpp View File

@ -46,60 +46,50 @@ struct DummyPin final : public BasePin {
struct relay_t { struct relay_t {
using pin_type = std::unique_ptr<BasePin>;
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<DummyPin>(GPIO_NONE), RELAY_TYPE_NORMAL, std::make_unique<DummyPin>(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 // 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<relay_t> _relays; std::vector<relay_t> _relays;
bool _relayRecursive = false; bool _relayRecursive = false;
size_t _relayDummy = 0; size_t _relayDummy = 0;
@ -1032,60 +1022,72 @@ void relaySetupWS() {
void relaySetupAPI() { 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<relayCount(); relayID++) {
apiRegister({
MQTT_TOPIC_RELAY, Api::Type::Json, ApiUnusedArg,
[](const Api&, JsonObject& root) {
JsonArray& relays = root.createNestedArray("relayStatus");
for (unsigned char id = 0; id < relayCount(); ++id) {
relays.add(_relays[id].target_status ? 1 : 0);
}
}
});
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _relays[relayID].target_status ? 1 : 0);
#if defined(ITEAD_SONOFF_IFAN02)
apiRegister({
MQTT_TOPIC_SPEED, Api::Type::Basic, ApiUnusedArg,
[](const Api&, ApiBuffer& buffer) {
snprintf(buffer.data, buffer.size, "%u", getSpeed());
}, },
[relayID](const char * payload) {
[](const Api&, ApiBuffer& buffer) {
setSpeed(atoi(buffer.data));
snprintf(buffer.data, buffer.size, "%u", getSpeed());
}
});
#endif
if (!_relayHandlePayload(relayID, payload)) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload);
char path[64] = {0};
for (unsigned char id = 0; id < relayCount(); ++id) {
sprintf_P(path, PSTR(MQTT_TOPIC_RELAY "/%u"), id);
apiRegister({
path, Api::Type::Basic, id,
[](const Api& api, ApiBuffer& buffer) {
snprintf_P(buffer.data, buffer.size, PSTR("%d"), _relays[api.arg].target_status ? 1 : 0);
},
[](const Api& api, ApiBuffer& buffer) {
if (!_relayHandlePayload(api.arg, buffer.data)) {
DEBUG_MSG_P(PSTR("[RELAY] Invalid API payload (%s)\n"), buffer.data);
return; return;
} }
} }
);
});
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_PULSE, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
dtostrf((double) _relays[relayID].pulse_ms / 1000, 1, 3, buffer);
sprintf_P(path, PSTR(MQTT_TOPIC_PULSE "/%u"), id);
apiRegister({
path, Api::Type::Basic, id,
[](const Api& api, ApiBuffer& buffer) {
dtostrf((double) _relays[api.arg].pulse_ms / 1000, 1, 3, buffer.data);
}, },
[relayID](const char * payload) {
unsigned long pulse = 1000 * atof(payload);
if (0 == pulse) return;
if (RELAY_PULSE_NONE != _relays[relayID].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), relayID);
[](const Api& api, ApiBuffer& buffer) {
unsigned long pulse = 1000 * atof(buffer.data);
if (0 == pulse) {
return;
} }
_relays[relayID].pulse_ms = pulse;
_relays[relayID].pulse = relayStatus(relayID) ? RELAY_PULSE_ON : RELAY_PULSE_OFF;
relayToggle(relayID, true, false);
}
);
#if defined(ITEAD_SONOFF_IFAN02)
apiRegister(MQTT_TOPIC_SPEED,
[relayID](char * buffer, size_t len) {
snprintf(buffer, len, "%u", getSpeed());
},
[relayID](const char * payload) {
setSpeed(atoi(payload));
if (RELAY_PULSE_NONE != _relays[api.arg].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), api.arg);
} }
);
#endif
_relays[api.arg].pulse_ms = pulse;
_relays[api.arg].pulse = relayStatus(api.arg)
? RELAY_PULSE_ON
: RELAY_PULSE_OFF;
relayToggle(api.arg, true, false);
}
});
} }
} }
@ -1482,9 +1484,9 @@ void _relaySetupAdhoc() {
} }
_relays.emplace_back( _relays.emplace_back(
std::make_unique<gpio_type>(_relayPin(id)),
new gpio_type(pin),
_relayType(id), _relayType(id),
std::make_unique<gpio_type>(_relayResetPin(id))
new gpio_type(_relayResetPin(id))
); );
} }


+ 82
- 60
code/espurna/rfbridge.cpp View File

@ -55,7 +55,13 @@ unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0};
unsigned char _uartpos = 0; unsigned char _uartpos = 0;
unsigned char _learnId = 0; unsigned char _learnId = 0;
bool _learnStatus = true;
enum class RfbLearn {
Disabled,
On,
Off
};
RfbLearn _learnStatus = RfbLearn::Disabled;
bool _rfbin = false; bool _rfbin = false;
struct rfb_message_t { struct rfb_message_t {
@ -66,7 +72,6 @@ static std::queue<rfb_message_t> _rfb_message_queue;
#if RFB_DIRECT #if RFB_DIRECT
RCSwitch * _rfModem; RCSwitch * _rfModem;
bool _learning = false;
#endif #endif
bool _rfb_receive = false; bool _rfb_receive = false;
@ -118,7 +123,7 @@ bool _rfbWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
} }
void _rfbWebSocketOnData(JsonObject& root) { void _rfbWebSocketOnData(JsonObject& root) {
_rfbWebSocketSendCodeArray(root, 0, relayCount());
_rfbWebSocketSendCodeArray(root, 0, relayCount());
} }
#endif // WEB_SUPPORT #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")); DEBUG_MSG_P(PSTR("[RF] Learn success\n"));
rfbStore(_learnId, _learnStatus, buffer);
rfbStore(_learnId, (_learnStatus == RfbLearn::On), buffer);
// Websocket update // Websocket update
#if WEB_SUPPORT #if WEB_SUPPORT
@ -213,7 +218,7 @@ void _rfbDecode() {
unsigned char id; unsigned char id;
unsigned char status; unsigned char status;
bool matched = _rfbMatch(buffer, id, status, buffer); bool matched = _rfbMatch(buffer, id, status, buffer);
if (matched) { if (matched) {
DEBUG_MSG_P(PSTR("[RF] Matched message '%s'\n"), buffer); DEBUG_MSG_P(PSTR("[RF] Matched message '%s'\n"), buffer);
_rfbin = true; _rfbin = true;
@ -320,7 +325,6 @@ void _rfbAckImpl() {}
void _rfbLearnImpl() { void _rfbLearnImpl() {
DEBUG_MSG_P(PSTR("[RF] Entering LEARN mode\n")); DEBUG_MSG_P(PSTR("[RF] Entering LEARN mode\n"));
_learning = true;
} }
void _rfbSendImpl(uint8_t * message) { void _rfbSendImpl(uint8_t * message) {
@ -351,10 +355,10 @@ void _rfbReceiveImpl() {
if (!_rfb_receive) return; if (!_rfb_receive) return;
static long learn_start = 0; static long learn_start = 0;
if (!_learning && learn_start) {
if ((_learnStatus == RfbLearn::Disabled) && learn_start) {
learn_start = 0; learn_start = 0;
} }
if (_learning) {
if (_learnStatus != RfbLearn::Disabled) {
if (!learn_start) { if (!learn_start) {
DEBUG_MSG_P(PSTR("[RF] Arming learn timeout\n")); DEBUG_MSG_P(PSTR("[RF] Arming learn timeout\n"));
learn_start = millis(); learn_start = millis();
@ -364,7 +368,7 @@ void _rfbReceiveImpl() {
memset(_uartbuf, 0, sizeof(_uartbuf)); memset(_uartbuf, 0, sizeof(_uartbuf));
_uartbuf[0] = RF_CODE_LEARN_KO; _uartbuf[0] = RF_CODE_LEARN_KO;
_rfbDecode(); _rfbDecode();
_learning = false;
_learnStatus = RfbLearn::Disabled;
} }
} }
@ -378,7 +382,7 @@ void _rfbReceiveImpl() {
unsigned int timing = _rfModem->getReceivedDelay(); unsigned int timing = _rfModem->getReceivedDelay();
memset(_uartbuf, 0, sizeof(_uartbuf)); memset(_uartbuf, 0, sizeof(_uartbuf));
unsigned char *msgbuf = _uartbuf + 1; 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[0] = 0xC0;
msgbuf[1] = _rfModem->getReceivedProtocol(); msgbuf[1] = _rfModem->getReceivedProtocol();
msgbuf[2] = timing >> 8; msgbuf[2] = timing >> 8;
@ -389,7 +393,7 @@ void _rfbReceiveImpl() {
msgbuf[7] = rf_code >> 8; msgbuf[7] = rf_code >> 8;
msgbuf[8] = rf_code >> 0; msgbuf[8] = rf_code >> 0;
_rfbDecode(); _rfbDecode();
_learning = false;
_learnStatus = RfbLearn::Disabled;
} }
} }
_rfModem->resetAvailable(); _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 #if MQTT_SUPPORT
void _rfbMqttCallback(unsigned int type, const char * topic, char * payload) { 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) { if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic); String t = mqttMagnitude((char *) topic);
// Check if should go into learn mode
if (t.startsWith(MQTT_TOPIC_RFLEARN)) { 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; return;
} }
if (t.equals(MQTT_TOPIC_RFOUT)) { if (t.equals(MQTT_TOPIC_RFOUT)) {
_rfbParseCode(payload); _rfbParseCode(payload);
return;
} }
#if !RFB_DIRECT #if !RFB_DIRECT
if (t.equals(MQTT_TOPIC_RFRAW)) { if (t.equals(MQTT_TOPIC_RFRAW)) {
_rfbParseRaw(payload); _rfbParseRaw(payload);
return;
} }
#endif #endif
@ -539,47 +566,42 @@ void _rfbMqttCallback(unsigned int type, const char * topic, char * payload) {
#if API_SUPPORT #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 #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 #endif
} }
@ -596,7 +618,7 @@ void _rfbInitCommands() {
terminalError(F("Wrong arguments")); terminalError(F("Wrong arguments"));
return; return;
} }
// 1st argument is relayID // 1st argument is relayID
int id = ctx.argv[1].toInt(); int id = ctx.argv[1].toInt();
if (id >= relayCount()) { if (id >= relayCount()) {
@ -617,7 +639,7 @@ void _rfbInitCommands() {
terminalError(F("Wrong arguments")); terminalError(F("Wrong arguments"));
return; return;
} }
// 1st argument is relayID // 1st argument is relayID
int id = ctx.argv[1].toInt(); int id = ctx.argv[1].toInt();
if (id >= relayCount()) { if (id >= relayCount()) {
@ -693,7 +715,7 @@ void rfbStatus(unsigned char id, bool status) {
void rfbLearn(unsigned char id, bool status) { void rfbLearn(unsigned char id, bool status) {
_learnId = id; _learnId = id;
_learnStatus = status;
_learnStatus = status ? RfbLearn::On : RfbLearn::Off;
_rfbLearnImpl(); _rfbLearnImpl();
} }
@ -723,7 +745,7 @@ void rfbSetup() {
#endif #endif
#if API_SUPPORT #if API_SUPPORT
_rfbAPISetup();
_rfbApiSetup();
#endif #endif
#if WEB_SUPPORT #if WEB_SUPPORT


+ 38
- 15
code/espurna/sensor.cpp View File

@ -1403,27 +1403,50 @@ void _sensorWebSocketOnConnected(JsonObject& root) {
#if API_SUPPORT #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) { 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 // API
#if API_SUPPORT #if API_SUPPORT
_sensorAPISetup();
_sensorApiSetup();
#endif #endif
// Terminal // Terminal


Loading…
Cancel
Save