Browse Source

rfbridge: handle rfb{ON,OFF} just like any other setting

removes the extra 'save' button from the ui and the code that handles it
also make sure to use the updated foreach_prefix when looking for the
match in the settings

resolve #2502
pull/2508/head
Maxim Prokhorov 2 years ago
parent
commit
28aa9622bb
25 changed files with 12970 additions and 12889 deletions
  1. BIN
      code/espurna/data/index.all.html.gz
  2. BIN
      code/espurna/data/index.curtain.html.gz
  3. BIN
      code/espurna/data/index.garland.html.gz
  4. BIN
      code/espurna/data/index.light.html.gz
  5. BIN
      code/espurna/data/index.lightfox.html.gz
  6. BIN
      code/espurna/data/index.rfbridge.html.gz
  7. BIN
      code/espurna/data/index.rfm69.html.gz
  8. BIN
      code/espurna/data/index.sensor.html.gz
  9. BIN
      code/espurna/data/index.small.html.gz
  10. BIN
      code/espurna/data/index.thermostat.html.gz
  11. +123
    -53
      code/espurna/rfbridge.cpp
  12. +1
    -1
      code/espurna/rfbridge.h
  13. +1896
    -1897
      code/espurna/static/index.all.html.gz.h
  14. +1079
    -1079
      code/espurna/static/index.curtain.html.gz.h
  15. +1388
    -1388
      code/espurna/static/index.garland.html.gz.h
  16. +1622
    -1622
      code/espurna/static/index.light.html.gz.h
  17. +1042
    -1041
      code/espurna/static/index.lightfox.html.gz.h
  18. +1075
    -1076
      code/espurna/static/index.rfbridge.html.gz.h
  19. +1079
    -1078
      code/espurna/static/index.rfm69.html.gz.h
  20. +1503
    -1502
      code/espurna/static/index.sensor.html.gz.h
  21. +1025
    -1025
      code/espurna/static/index.small.html.gz.h
  22. +1062
    -1061
      code/espurna/static/index.thermostat.html.gz.h
  23. +31
    -22
      code/html/custom.css
  24. +24
    -22
      code/html/custom.js
  25. +20
    -22
      code/html/index.html

BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.curtain.html.gz View File


BIN
code/espurna/data/index.garland.html.gz View File


BIN
code/espurna/data/index.light.html.gz View File


BIN
code/espurna/data/index.lightfox.html.gz View File


BIN
code/espurna/data/index.rfbridge.html.gz View File


BIN
code/espurna/data/index.rfm69.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


BIN
code/espurna/data/index.small.html.gz View File


BIN
code/espurna/data/index.thermostat.html.gz View File


+ 123
- 53
code/espurna/rfbridge.cpp View File

@ -331,6 +331,60 @@ void _rfbLearnImpl();
void _rfbReceiveImpl();
void _rfbSendImpl(const RfbMessage& message);
#if RELAY_SUPPORT
namespace rfbridge {
namespace settings {
namespace keys {
alignas(4) static constexpr char On[] PROGMEM = "rfbON";
alignas(4) static constexpr char Off[] PROGMEM = "rfbOFF";
} // namespace keys
String off(size_t id) {
return getSetting({FPSTR(keys::Off), id});
}
String on(size_t id) {
return getSetting({FPSTR(keys::On), id});
}
void store(const __FlashStringHelper* prefix, size_t id, const String& value) {
SettingsKey key { prefix, id };
setSetting(key, value);
DEBUG_MSG_P(PSTR("[RF] Saved %s => \"%s\"\n"), key.c_str(), value.c_str());
}
void off(size_t id, const String& value) {
store(FPSTR(keys::Off), id, value);
}
void on(size_t id, const String& value) {
store(FPSTR(keys::On), id, value);
}
} // namespace settings
} // namespace rfbridge
void _rfbStore(size_t id, bool status, const String& code) {
if (status) {
rfbridge::settings::on(id, code);
} else {
rfbridge::settings::off(id, code);
}
}
String _rfbRetrieve(size_t id, bool status) {
if (status) {
return rfbridge::settings::on(id);
} else {
return rfbridge::settings::off(id);
}
}
#endif
// -----------------------------------------------------------------------------
// WEBUI INTEGRATION
// -----------------------------------------------------------------------------
@ -354,8 +408,8 @@ void _rfbWebSocketSendCodeArray(JsonObject& root, size_t start, size_t size) {
for (auto id = start; id < (start + size); ++id) {
JsonArray& pair = codes.createNestedArray();
pair.add(rfbRetrieve(id, false));
pair.add(rfbRetrieve(id, true));
pair.add(rfbridge::settings::off(id));
pair.add(rfbridge::settings::on(id));
}
}
@ -376,11 +430,30 @@ void _rfbWebSocketOnConnected(JsonObject& root) {
#endif
}
void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
void _rfbWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) {
#if RELAY_SUPPORT
if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
if (strncmp(action, "rfb", 3) != 0) {
return;
}
auto idValue = data[F("id")];
if (!idValue.success()) {
return;
}
auto statusValue = data[F("status")];
if (!statusValue.success()) {
return;
}
const size_t id { idValue.as<size_t>() };
const size_t status { statusValue.as<bool>() };
if (STRING_VIEW("rfblearn") == action) {
rfbLearn(id, status);
} else if (STRING_VIEW("rfbforget") == action) {
rfbForget(id, status);
}
#endif
}
@ -421,67 +494,66 @@ bool _rfbCompare(const char* lhs, const char* rhs, size_t length) {
// previous implementation tried to help MQTT / API requests to match based on the saved code,
// thus requiring us to 'return' value from settings as the real code, replacing input
RfbRelayMatch _rfbMatch(const char* code) {
RfbRelayMatch matched;
if (!relayCount()) {
return {};
return matched;
}
const auto len = strlen(code);
::settings::StringView codeView { code };
// we gather all available options, as the kv store might be defined in any order
// scan kvs only once, since we want both ON and OFF options and don't want to depend on the relayCount()
RfbRelayMatch matched;
settings::internal::foreach([code, len, &matched](settings::kvs_type::KeyValueResult&& kv) {
const auto key = kv.key.read();
PayloadStatus status = key.startsWith(F("rfbON"))
? PayloadStatus::On : key.startsWith(F("rfbOFF"))
? PayloadStatus::Off : PayloadStatus::Unknown;
if (PayloadStatus::Unknown == status) {
return;
}
settings::internal::foreach_prefix(
[codeView, &matched](settings::StringView prefix, String key, const settings::kvs_type::ReadResult& value) {
if (codeView.length() != value.length()) {
return;
}
const auto value = kv.value.read();
if (len != value.length()) {
return;
}
PayloadStatus status {
(prefix.c_str() == &rfbridge::settings::keys::On[0]) ? PayloadStatus::On :
(prefix.c_str() == &rfbridge::settings::keys::Off[0]) ? PayloadStatus::Off :
PayloadStatus::Unknown };
if (!_rfbCompare(code, value.c_str(), len)) {
return;
}
if (PayloadStatus::Unknown == status) {
return;
}
// note: strlen is constexpr here
const char* id_ptr = key.c_str() + (
(PayloadStatus::On == status) ? strlen("rfbON") : strlen("rfbOFF"));
if (*id_ptr == '\0') {
return;
}
if (!_rfbCompare(codeView.c_str(), value.read().c_str(), codeView.length())) {
return;
}
size_t id;
if (!tryParseId(id_ptr, relayCount, id)) {
return;
}
const char* id_ptr = key.c_str() + prefix.length();
if (*id_ptr == '\0') {
return;
}
// when we see the same id twice, we match the opposite statuses
if (matched && (id == matched.id())) {
matched.reset(matched.id(), PayloadStatus::Toggle);
return;
}
size_t id;
if (!tryParseId(id_ptr, relayCount, id)) {
return;
}
matched.reset(matched ? std::min(id, matched.id()) : id, status);
});
// when we see the same id twice, we match the opposite statuses
if (matched && (id == matched.id())) {
matched.reset(matched.id(), PayloadStatus::Toggle);
return;
}
matched.reset(matched ? std::min(id, matched.id()) : id, status);
},
{
rfbridge::settings::keys::On,
rfbridge::settings::keys::Off
});
return matched;
}
void _rfbLearnFromString(std::unique_ptr<RfbLearn>& learn, const char* buffer) {
if (!learn) return;
DEBUG_MSG_P(PSTR("[RF] Learned relay ID %u after %u ms\n"), learn->id, millis() - learn->ts);
rfbStore(learn->id, learn->status, buffer);
_rfbStore(learn->id, learn->status, buffer);
// Websocket update needs to happen right here, since the only time
// we send these in bulk is at the very start of the connection
@ -1161,18 +1233,16 @@ void _rfbInitCommands() {
// PUBLIC
// -----------------------------------------------------------------------------
void rfbStore(size_t id, bool status, const char * code) {
SettingsKey key { status ? F("rfbON") : F("rfbOFF"), id };
setSetting(key, code);
DEBUG_MSG_P(PSTR("[RF] Saved %s => \"%s\"\n"), key.c_str(), code);
#if RELAY_SUPPORT
void rfbStore(size_t id, bool status, String code) {
_rfbStore(id, status, std::move(code));
}
String rfbRetrieve(size_t id, bool status) {
return getSetting({ status ? F("rfbON") : F("rfbOFF"), id });
return _rfbRetrieve(id, status);
}
#if RELAY_SUPPORT
void rfbStatus(size_t id, bool status) {
// TODO: This is a left-over from the old implementation. Right now we set this lock when relay handler
// is called within the receiver, while this is called from either relayStatus or relay loop calling
@ -1180,7 +1250,7 @@ void rfbStatus(size_t id, bool status) {
// TODO: Consider having 'origin' of the relay change. Either supply relayStatus with an additional arg,
// or track these statuses directly.
if (!_rfb_relay_status_lock[id]) {
rfbSend(rfbRetrieve(id, status));
rfbSend(_rfbRetrieve(id, status));
}
_rfb_relay_status_lock[id] = false;


+ 1
- 1
code/espurna/rfbridge.h View File

@ -21,7 +21,7 @@ void rfbStatus(size_t id, bool status);
void rfbLearn(size_t id, bool status);
String rfbRetrieve(size_t id, bool status);
void rfbStore(size_t id, bool status, const char* code);
void rfbStore(size_t id, bool status, String code);
void rfbForget(size_t id, bool status);
void rfbSetup();

+ 1896
- 1897
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 1079
- 1079
code/espurna/static/index.curtain.html.gz.h
File diff suppressed because it is too large
View File


+ 1388
- 1388
code/espurna/static/index.garland.html.gz.h
File diff suppressed because it is too large
View File


+ 1622
- 1622
code/espurna/static/index.light.html.gz.h
File diff suppressed because it is too large
View File


+ 1042
- 1041
code/espurna/static/index.lightfox.html.gz.h
File diff suppressed because it is too large
View File


+ 1075
- 1076
code/espurna/static/index.rfbridge.html.gz.h
File diff suppressed because it is too large
View File


+ 1079
- 1078
code/espurna/static/index.rfm69.html.gz.h
File diff suppressed because it is too large
View File


+ 1503
- 1502
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 1025
- 1025
code/espurna/static/index.small.html.gz.h
File diff suppressed because it is too large
View File


+ 1062
- 1061
code/espurna/static/index.thermostat.html.gz.h
File diff suppressed because it is too large
View File


+ 31
- 22
code/html/custom.css View File

@ -248,7 +248,7 @@ label {
.button-wifi-scan,
.button-more-parent,
.button-simple-action,
.button-rfb-send {
.button-rfb-save {
background: rgb(255, 128, 0); /* orange */
}
@ -396,6 +396,36 @@ input[disabled] + .toggle .toggle__handler {
background: #ccc;
}
/* -----------------------------------------------------------------------------
RF Bridge panel
-------------------------------------------------------------------------- */
#rfbNodes label {
width: 2em;
margin: 0 0 0 0;
}
@media (min-width: 48em) {
#rfbNodes input {
width: 70%;
margin: 0 1em 10px 1em;
}
#rfbNodes .pure-button-group {
letter-spacing: 0.15em;
}
}
@media (max-width: 48em) {
#rfbNodes input {
width: 100%;
}
#rfbNodes button {
width: 50%;
}
}
/* -----------------------------------------------------------------------------
Loading
-------------------------------------------------------------------------- */
@ -435,27 +465,6 @@ input[disabled] + .toggle .toggle__handler {
padding-top: 1em;
}
/* -----------------------------------------------------------------------------
RF Bridge panel
-------------------------------------------------------------------------- */
#panel-rfb fieldset {
margin: 10px 2px;
padding: 20px;
}
#panel-rfb input {
margin-right: 5px;
}
#panel-rfb label {
padding-top: 5px;
}
#panel-rfb input {
text-align: center;
}
/* -----------------------------------------------------------------------------
Admin panel
-------------------------------------------------------------------------- */


+ 24
- 22
code/html/custom.js View File

@ -2242,17 +2242,16 @@ function updateChannels(channels) {
function rfbAction(event) {
const prefix = "button-rfb-";
let [action] = Array.from(event.target.classList)
const [buttonRfbClass] = Array.from(event.target.classList)
.filter(x => x.startsWith(prefix));
if (action) {
let container = event.target.parentElement.parentElement;
let input = container.querySelector("input");
if (buttonRfbClass) {
const container = event.target.parentElement.parentElement;
const input = container.querySelector("input");
action = action.replace(prefix, "");
sendAction(`rfb${action}`, {
id: input.dataset["id"],
status: input.dataset["status"]
sendAction(`rfb${buttonRfbClass.replace(prefix, "")}`, {
id: parseInt(input.dataset["id"], 10),
status: input.name === "rfbON"
});
}
}
@ -2261,32 +2260,35 @@ function rfbAdd() {
let container = document.getElementById("rfbNodes");
const id = container.childElementCount;
let line = loadTemplate("rfb-node");
const line = loadConfigTemplate("rfb-node");
line.querySelector("span").textContent = id;
for (let input of line.querySelectorAll("input")) {
input.dataset["id"] = id;
input.setAttribute("id", `${input.name}${id}`);
}
elementSelectorOnClick(".button-rfb-learn", rfbAction);
elementSelectorOnClick(".button-rfb-forget", rfbAction);
elementSelectorOnClick(".button-rfb-send", rfbAction);
for (let action of ["learn", "forget"]) {
for (let button of line.querySelectorAll(`.button-rfb-${action}`)) {
button.addEventListener("click", rfbAction);
}
}
mergeTemplate(container, line);
return false;
}
function rfbSelector(id, status) {
return `input[name='rfbcode'][data-id='${id}'][data-status='${status}']`;
}
function rfbHandleCodes(value) {
value.codes.forEach((codes, id) => {
let realId = id + value.start;
let [off, on] = codes;
document.querySelector(rfbSelector(realId, 0)).value = off;
document.querySelector(rfbSelector(realId, 1)).value = on;
const realId = id + value.start;
const [off, on] = codes;
const rfbOn = document.getElementById(`rfbON${realId}`);
setInputValue(rfbOn, on);
const rfbOff = document.getElementById(`rfbOFF${realId}`);
setInputValue(rfbOff, off);
setOriginalsFromValues([rfbOn, rfbOff]);
});
}


+ 20
- 22
code/html/index.html View File

@ -1957,7 +1957,7 @@
<fieldset>
<legend>RF Codes</legend>
<div id="rfbNodes"></div>
<div id="rfbNodes" class="settings-group pure-form-aligned" data-settings-schema="rfbON rfbOFF"></div>
</fieldset>
<fieldset>
@ -2071,27 +2071,25 @@
<!-- removeIf(!rfbridge) -->
<template id="template-rfb-node" >
<div class="pure-control-group">
<fieldset>
<legend>Switch #<span></span></legend>
<div class="pure-control-group">
<label>Switch <strong>ON</strong></label>
<input class="pure-input-1" type="text" maxlength="116" name="rfbcode" data-status="1" data-settings-ignore="true">
<button type="button" class="pure-input-1-4 pure-button button-rfb-learn">LEARN</button>
<button type="button" class="pure-input-1-4 pure-button button-rfb-send">SAVE</button>
<button type="button" class="pure-input-1-4 pure-button button-rfb-forget">FORGET</button>
</div>
<div class="pure-control-group">
<div><label>Switch <strong>OFF</strong></label></div>
<input class="pure-input-1" type="text" maxlength="116" name="rfbcode" data-status="0" data-settings-ignore="true">
</div>
<div class="pure-button-group">
<button type="button" class="pure-input-1-4 pure-button button-rfb-learn">LEARN</button>
<button type="button" class="pure-input-1-4 pure-button button-rfb-send">SAVE</button>
<button type="button" class="pure-input-1-4 pure-button button-rfb-forget">FORGET</button>
</div>
</fieldset>
</div>
<fieldset>
<legend>Switch #<span></span></legend>
<div class="pure-control-group">
<label>ON</label>
<input type="text" maxlength="116" name="rfbON">
<span class="pure-button-group">
<button type="button" class="pure-button button-rfb-learn">LEARN</button>
<button type="button" class="pure-button button-rfb-forget">FORGET</button>
</span>
</div>
<div class="pure-control-group">
<label>OFF</label>
<input type="text" maxlength="116" name="rfbOFF">
<span class="pure-button-group">
<button type="button" class="pure-button button-rfb-learn">LEARN</button>
<button type="button" class="pure-button button-rfb-forget">FORGET</button>
</span>
</div>
</fieldset>
</template>
<!-- endRemoveIf(!rfbridge) -->


Loading…
Cancel
Save