Files
evan bf286eefa6 feat(ble): expose isInitialized and reclaim memory before radios start
The controller and the WiFi driver each need a large aggregate allocation,
which a live app sitting on garbage can deny - that is why a failed connect
often succeeded on retry. Both bindings now collect before initializing, and
the tree reserves its node and spec capacity so a build does not reallocate
into a tight heap.
2026-08-04 13:10:18 -04:00

385 lines
12 KiB
C++

#pragma once
// Host doubles for every provider, deliberately dumb: they record what a
// binding asked for and hand back canned values, so a test asserts the
// marshalling rather than a device.
#include <map>
#include <string>
#include <vector>
#include <lua/providers.h>
namespace fake {
using namespace esp32lua;
struct Log : LogProvider {
LogLevel level = LogLevel::Debug;
std::string message;
void write(LogLevel nextLevel, const std::string& nextMessage) override {
level = nextLevel;
message = nextMessage;
}
};
struct Settings : SettingsProvider {
int32_t degrees = 0;
std::string tz = "UTC0";
int32_t rotation() const override { return degrees; }
Status setRotation(int32_t value) override {
degrees = value;
return Status::success();
}
std::string timezone() const override { return tz; }
Status setTimezone(const std::string& value) override {
tz = value;
return Status::success();
}
};
struct Sys : SysProvider {
int32_t millis() const override { return 1234; }
MemoryInfo memory() const override {
const MemoryInfo info = {100, 200, 50};
return info;
}
bool isClockSynced() const override { return true; }
};
struct Fs : FsProvider {
std::map<std::string, std::string> files;
std::string written;
// One buffer handed to the runtime, which owns and deletes it.
struct Reader : FileReader {
std::string content;
size_t at = 0;
int32_t read(char* out, int32_t maxBytes) override {
const size_t remaining = content.size() - at;
const size_t count = remaining < static_cast<size_t>(maxBytes)
? remaining
: static_cast<size_t>(maxBytes);
content.copy(out, count, at);
at += count;
return static_cast<int32_t>(count);
}
};
FileReader* openRead(const std::string& path) override {
const std::map<std::string, std::string>::const_iterator found =
files.find(path);
if (found == files.end())
return nullptr;
Reader* reader = new Reader();
reader->content = found->second;
return reader;
}
bool exists(const std::string& path) const override {
return files.count(path) != 0;
}
Status fileSize(const std::string& path, int32_t& size) const override {
const std::map<std::string, std::string>::const_iterator found =
files.find(path);
if (found == files.end())
return Status::failure("no such file");
size = static_cast<int32_t>(found->second.size());
return Status::success();
}
Status listDirs(const std::string&,
std::vector<std::string>& names) const override {
names.push_back("apps");
return Status::success();
}
Status listFiles(const std::string&,
std::vector<std::string>& names) const override {
names.push_back("main.lua");
return Status::success();
}
Status mkdir(const std::string&) override { return Status::success(); }
Status readFile(const std::string& path, int32_t maxBytes,
std::string& content) const override {
const std::map<std::string, std::string>::const_iterator found =
files.find(path);
if (found == files.end())
return Status::failure("no such file");
if (static_cast<int32_t>(found->second.size()) > maxBytes)
return Status::failure("too large");
content = found->second;
return Status::success();
}
Status readLineAt(const std::string& path, int32_t offset, int32_t,
bool& found, std::string& line,
int32_t& nextOffset) const override {
const std::map<std::string, std::string>::const_iterator file =
files.find(path);
if (file == files.end())
return Status::failure("no such file");
if (offset >= static_cast<int32_t>(file->second.size())) {
found = false;
return Status::success();
}
const size_t end = file->second.find('\n', offset);
line = file->second.substr(
offset, end == std::string::npos ? std::string::npos : end - offset);
nextOffset = end == std::string::npos
? static_cast<int32_t>(file->second.size())
: static_cast<int32_t>(end + 1);
found = true;
return Status::success();
}
Status remove(const std::string& path) override {
files.erase(path);
return Status::success();
}
Status removeTree(const std::string&) override { return Status::success(); }
Status rename(const std::string&, const std::string&) override {
return Status::success();
}
Status writeFile(const std::string& path,
const std::string& content) override {
files[path] = content;
written = content;
return Status::success();
}
};
struct Gui : GuiProvider {
std::string trace;
int32_t degrees = 0;
bool fullscreen = false;
bool gradient = false;
FontIds fonts() const override {
const FontIds ids = {1, 2, 3, 4, 0, 1};
return ids;
}
int32_t width() const override { return 320; }
int32_t height() const override { return 240; }
int32_t rotation() const override { return degrees; }
void setRotation(int32_t value) override { degrees = value; }
int32_t color(int32_t r, int32_t g, int32_t b) const override {
return (r << 16) | (g << 8) | b;
}
void clear(int32_t) override { trace += "clear;"; }
void fillRect(int32_t, int32_t, int32_t, int32_t, int32_t) override {
trace += "fillRect;";
}
void drawRect(int32_t, int32_t, int32_t, int32_t, int32_t) override {
trace += "drawRect;";
}
void drawLine(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) override {
trace += "drawLine;";
}
void drawPixel(int32_t, int32_t, int32_t) override { trace += "drawPixel;"; }
void drawCircle(int32_t, int32_t, int32_t, int32_t, int32_t) override {
trace += "drawCircle;";
}
void fillCircle(int32_t, int32_t, int32_t, int32_t, const int32_t*) override {
trace += "fillCircle;";
}
void roundRect(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t,
const int32_t* top, const int32_t* bottom,
const int32_t* border) override {
if (top && bottom && *top != *bottom)
gradient = true;
trace += "roundRect(";
trace += top ? "fill" : "-";
trace += border ? ",border" : ",-";
trace += ");";
}
void setFullscreen(bool on) override { fullscreen = on; }
// Stands in for an e-ink panel, where a second commit is a second visible
// refresh.
void commit() override { commits++; }
int commits = 0;
void fillPolygon(const int32_t*, const int32_t*, size_t count,
int32_t) override {
trace += "fillPolygon" + std::to_string(count) + ";";
}
Status drawBmp(const std::string&, const int32_t*, const int32_t*,
const int32_t*, const int32_t*) override {
trace += "drawBmp;";
return Status::success();
}
int32_t textWidth(int32_t, const std::string& text, int32_t) const override {
return static_cast<int32_t>(text.size()) * 8;
}
int32_t fontHeight(int32_t, int32_t) const override { return 16; }
void drawText(int32_t, int32_t, int32_t, const std::string& text, int32_t,
int32_t, const int32_t*) override {
trace += "drawText(" + text + ");";
}
};
struct Http : HttpProvider {
std::string method;
std::string body;
std::vector<HttpHeader> headers;
int32_t limit = 0;
Status request(const std::string& nextMethod, const std::string&,
const std::string& nextBody,
const std::vector<HttpHeader>& nextHeaders, int32_t maxBytes,
HttpResponse& response) override {
method = nextMethod;
body = nextBody;
headers = nextHeaders;
limit = maxBytes;
response.status = 404;
response.body = "missing";
return Status::success();
}
Status download(const std::string&, const std::string&,
const HttpDownload& options, int32_t& bytesWritten) override {
bytesWritten = options.maxBytes;
return Status::success();
}
};
struct Timer : TimerProvider {
std::vector<TimerId> scheduled;
std::vector<TimerId> cancelled;
Status schedule(TimerId id, int32_t, bool) override {
scheduled.push_back(id);
return Status::success();
}
void cancel(TimerId id) override { cancelled.push_back(id); }
};
struct Wifi : WifiProvider {
WifiStatus current;
bool savedReconnect = false;
Wifi() {
current.state = "connected";
current.ssid = "home";
current.ip = "192.168.1.5";
current.rssi = -50;
}
Status scan(std::vector<WifiNetwork>& networks) override {
const WifiNetwork network = {"home", -50, true};
networks.push_back(network);
return Status::success();
}
Status connect(const std::string* ssid, const std::string*) override {
savedReconnect = ssid == nullptr;
return Status::success();
}
WifiStatus status() const override { return current; }
void disconnect() override { current.state = "disconnected"; }
Status forget() override { return Status::success(); }
};
struct Ble : BleProvider {
int32_t duration = 0;
std::string value;
bool initialized = false;
Status init(const std::string*) override {
initialized = true;
return Status::success();
}
void deinit() override { initialized = false; }
bool isInitialized() const override { return initialized; }
Status scan(int32_t durationMs, std::vector<BleDevice>& devices) override {
duration = durationMs;
const BleDevice device = {"tag", "aa:bb", -60};
devices.push_back(device);
return Status::success();
}
Status connect(const std::string&) override { return Status::success(); }
void disconnect() override {}
bool isConnected() const override { return true; }
Status read(const std::string&, const std::string&,
std::string& out) override {
out = std::string("a\0b", 3);
return Status::success();
}
Status write(const std::string&, const std::string&,
const std::string& next) override {
value = next;
return Status::success();
}
Status startAdvertising(const std::string*) override {
return Status::success();
}
void stopAdvertising() override {}
};
struct Touch : TouchProvider {
bool down = true;
int32_t calibration[4] = {0, 0, 0, 0};
bool touch(int32_t& x, int32_t& y) const override {
x = 10;
y = 20;
return down;
}
bool rawTouch(int32_t& x, int32_t& y) const override {
x = 300;
y = 700;
return down;
}
bool isTouched() const override { return down; }
Status setCalibration(int32_t x0, int32_t y0, int32_t x1,
int32_t y1) override {
calibration[0] = x0;
calibration[1] = y0;
calibration[2] = x1;
calibration[3] = y1;
return Status::success();
}
};
struct Buttons : ButtonsProvider {
std::vector<std::string> buttons() const override {
std::vector<std::string> roles;
roles.push_back("left");
roles.push_back("right");
roles.push_back("confirm");
roles.push_back("back");
return roles;
}
bool isAnyPressed() const override { return false; }
bool isPressed(const std::string& button) const override {
return button == "confirm";
}
bool wasPressed(const std::string&) const override { return false; }
bool wasReleased(const std::string&) const override { return false; }
};
// Every provider a Runtime needs, so a test names only what it asserts on.
struct Bench {
Log log;
Settings settings;
Sys sys;
Fs fs;
Gui gui;
Http http;
Timer timer;
Wifi wifi;
Ble ble;
Touch touch;
Buttons buttons;
Providers providers() {
Providers providers;
providers.log = &log;
providers.settings = &settings;
providers.sys = &sys;
providers.fs = &fs;
providers.gui = &gui;
providers.http = &http;
providers.timer = &timer;
providers.wifi = &wifi;
providers.ble = &ble;
providers.touch = &touch;
providers.buttons = &buttons;
return providers;
}
};
} // namespace fake