176 lines
7.9 KiB
C++
176 lines
7.9 KiB
C++
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <helpers/CLICommandUtils.h>
|
|
#include <helpers/CompanionHardwareCommandCompat.h>
|
|
#include <helpers/radiolib/RXPowerSaving.h>
|
|
#include "usb_mode_console.h"
|
|
|
|
namespace mesh {enum class RadioParamApplyResult {APPLIED,BUSY,FAILED};}
|
|
struct Prefs {
|
|
uint8_t rx_powersaving_enabled = 0, rx_ps_level = 8, rx_ps_preamble = 16, sf = 7;
|
|
uint32_t rx_ps_rx_us = 3000, rx_ps_sleep_us = 4000;
|
|
float bw = 125;
|
|
};
|
|
static bool same(const Prefs& a, const Prefs& b) {
|
|
return a.rx_powersaving_enabled == b.rx_powersaving_enabled
|
|
&& a.rx_ps_level == b.rx_ps_level && a.rx_ps_preamble == b.rx_ps_preamble
|
|
&& a.rx_ps_rx_us == b.rx_ps_rx_us && a.rx_ps_sleep_us == b.rx_ps_sleep_us
|
|
&& a.sf == b.sf && a.bw == b.bw;
|
|
}
|
|
struct Driver {
|
|
bool supported = true, enabled = false;
|
|
unsigned calls = 0;
|
|
uint32_t rx = 3000, sleep = 4000;
|
|
std::deque<bool> results;
|
|
mesh::RadioParamApplyResult trySetRxPowerSaving(bool enable,uint32_t rx,uint32_t sleep) {
|
|
return setRxPowerSaving(enable,rx,sleep)?mesh::RadioParamApplyResult::APPLIED:mesh::RadioParamApplyResult::FAILED;
|
|
}
|
|
bool supportsRxPowerSaving() const { return supported; }
|
|
bool setRxPowerSaving(bool enable, uint32_t receive, uint32_t rest) {
|
|
++calls;
|
|
const bool accepted = results.empty() ? true : results.front();
|
|
if (!results.empty()) results.pop_front();
|
|
if (accepted) { enabled = enable; rx = receive; sleep = rest; }
|
|
return accepted;
|
|
}
|
|
} radio_driver;
|
|
struct BaseSerialInterface {};
|
|
struct SerialTransport {
|
|
BaseSerialInterface requester, other;
|
|
BaseSerialInterface* current = &requester;
|
|
BaseSerialInterface* reply_route = nullptr;
|
|
std::vector<uint8_t> reply;
|
|
BaseSerialInterface* captureReplyRoute() { return current; }
|
|
size_t writeFrameToRoute(BaseSerialInterface* route, const uint8_t* data, size_t size) {
|
|
reply_route = route;
|
|
reply.assign(data, data + size);
|
|
return size;
|
|
}
|
|
};
|
|
struct MyMesh {
|
|
Prefs _prefs, durable;
|
|
SerialTransport serial;
|
|
SerialTransport* _serial = &serial;
|
|
bool _radio_available = true, storage_accepts = true, saved_radio_apply_pending = false;
|
|
bool _primary_radio_blocked=false;
|
|
unsigned saves = 0;
|
|
uint32_t radio_apply_retry_at = 0;
|
|
uint8_t radio_apply_failures = 0;
|
|
uint8_t cmd_frame[180] = {}, out_frame[180] = {};
|
|
char reply_buf[160] = {};
|
|
MyMesh() { radio_driver = Driver(); }
|
|
bool savePrefs() { ++saves; if (!storage_accepts) return false; durable = _prefs; return true; }
|
|
void queueSavedRadioApply();
|
|
bool applyAndSaveRxPowerSaving(const char*, char*);
|
|
bool handleLocalControlCommand(const char*, char*, size_t);
|
|
bool handleCommand(const char*, uint32_t, char*);
|
|
void frame(size_t len) { @FRAME@ }
|
|
void beginUsbLoggingReplyBarrier(BaseSerialInterface* route) {
|
|
assert(route == &serial.requester);
|
|
// Other transport activity must not steal the response after capture.
|
|
serial.current = &serial.other;
|
|
}
|
|
void endUsbLoggingReplyBarrier(bool queued) { assert(queued); }
|
|
void writeErrFrame(uint8_t) { assert(false && "Expected a textual CLI reply"); }
|
|
std::string run(const char* command, bool nul = true) {
|
|
const size_t size = strlen(command);
|
|
assert(size + 2 < sizeof(cmd_frame));
|
|
cmd_frame[0] = mesh::companion::kRunCliCommand;
|
|
memcpy(cmd_frame + 1, command, size);
|
|
if (nul) cmd_frame[size + 1] = 0;
|
|
frame(size + 1 + unsigned(nul));
|
|
assert(serial.reply_route == &serial.requester);
|
|
assert(!serial.reply.empty() && serial.reply[0] == RESP_CODE_CLI_REPLY);
|
|
return std::string(serial.reply.begin() + 1, serial.reply.end());
|
|
}
|
|
};
|
|
@METHODS@
|
|
|
|
static void configure(MyMesh& node, unsigned fault) {
|
|
if (fault == 1) radio_driver.supported = false;
|
|
if (fault == 2) radio_driver.results = {false};
|
|
if (fault == 3) node.storage_accepts = false;
|
|
}
|
|
static bool error(const std::string& reply) {
|
|
return reply.find("Error") != std::string::npos || reply.find("ERROR") != std::string::npos;
|
|
}
|
|
int main() {
|
|
for (bool nul : {false, true}) {
|
|
for (const char* value : {"off", "on", "level 3 preamble 16", "level 8 preamble 32", "65625 60000"}) {
|
|
MyMesh node;
|
|
const std::string command = std::string(" A7| \tset radio.rxps \t") + value;
|
|
const std::string reply = node.run(command.c_str(), nul);
|
|
assert(reply.find("A7|OK - ") == 0 && node.saves == 1 && radio_driver.calls == 1);
|
|
assert(same(node._prefs, node.durable));
|
|
assert(radio_driver.enabled == bool(node.durable.rx_powersaving_enabled));
|
|
assert(radio_driver.rx == node.durable.rx_ps_rx_us && radio_driver.sleep == node.durable.rx_ps_sleep_us);
|
|
if (!strcmp(value, "on")) assert(node.durable.rx_ps_level == 3);
|
|
if (!strcmp(value, "off")) assert(!node.durable.rx_powersaving_enabled);
|
|
if (!strcmp(value, "level 3 preamble 16"))
|
|
assert(node.durable.rx_ps_level == 3 && node.durable.rx_ps_preamble == 16);
|
|
if (!strcmp(value, "65625 60000"))
|
|
assert(node.durable.rx_ps_level == 0 && node.durable.rx_ps_rx_us == 65625 && node.durable.rx_ps_sleep_us == 60000);
|
|
}
|
|
}
|
|
for (const char* command : {"set radio.rxps", "set radio.rxps on extra", "set radio.rxps level 0",
|
|
"set radio.rxps level 3 preamble 17", "set radio.rxps 1000 -1"}) {
|
|
MyMesh node;
|
|
const std::string reply = node.run(command);
|
|
assert(error(reply) && reply.find("Unknown") == std::string::npos);
|
|
assert(node.saves == 0 && radio_driver.calls == 0 && same(node._prefs, Prefs()));
|
|
}
|
|
for (unsigned fault : {1u, 2u, 3u}) {
|
|
MyMesh node;
|
|
configure(node, fault);
|
|
const std::string reply = node.run("B2|set radio.rxps level 3 preamble 16");
|
|
assert(reply.find("B2|") == 0 && error(reply) && reply.find("Unknown") == std::string::npos);
|
|
assert(same(node._prefs, Prefs()) && same(node.durable, Prefs()));
|
|
assert(node.saves == unsigned(fault == 3));
|
|
assert(radio_driver.calls == (fault == 1 ? 0u : fault == 2 ? 1u : 2u));
|
|
assert(!radio_driver.enabled && radio_driver.rx == 3000 && radio_driver.sleep == 4000);
|
|
}
|
|
{
|
|
MyMesh node;
|
|
node.storage_accepts = false;
|
|
radio_driver.results = {true, false};
|
|
assert(error(node.run("set radio.rxps level 3 preamble 16")));
|
|
assert(node.saved_radio_apply_pending && same(node._prefs, Prefs()) && same(node.durable, Prefs()));
|
|
}
|
|
// Every shared-route response remains bounded, including errors from the
|
|
// original fixed-size setter. A zero-capacity call must have no side effects.
|
|
for (unsigned fault : {0u, 1u, 2u, 3u, 4u}) {
|
|
const char* command = fault == 4 ? "set radio.rxps level 3 preamble 17"
|
|
: "set radio.rxps level 3 preamble 16";
|
|
MyMesh reference;
|
|
configure(reference, fault);
|
|
char full[160];
|
|
assert(reference.handleLocalControlCommand(command, full, sizeof(full)));
|
|
const std::string expected = full;
|
|
for (size_t capacity = 0; capacity <= 180; ++capacity) {
|
|
MyMesh node;
|
|
configure(node, fault);
|
|
std::vector<unsigned char> bytes(capacity + 16, 0xA7);
|
|
char* reply = reinterpret_cast<char*>(bytes.data() + 8);
|
|
assert(node.handleLocalControlCommand(command, reply, capacity) == (capacity != 0));
|
|
if (capacity) {
|
|
const size_t count = std::min(expected.size(), capacity - 1);
|
|
assert(strlen(reply) == count && !memcmp(reply, expected.data(), count));
|
|
} else assert(node.saves == 0 && radio_driver.calls == 0);
|
|
for (size_t i = 0; i < 8; ++i) assert(bytes[i] == 0xA7);
|
|
for (size_t i = 8 + capacity; i < bytes.size(); ++i) assert(bytes[i] == 0xA7);
|
|
}
|
|
}
|
|
for (const char* command : {"set radio.rxpsx on", "set radio.rxps.status on", "set radio.rxps.rfrx_disabled on"}) {
|
|
MyMesh node;
|
|
const std::string reply = node.run(command);
|
|
assert(reply == "Unknown command" && node.saves == 0 && radio_driver.calls == 0);
|
|
}
|
|
puts("RXPS framed setter and bounded shared-route contracts passed");
|
|
}
|