feat(firmware): add 'user data' section to model yaml for Lua scripts (#7562)
Co-authored-by: Peter Feerick <peter.feerick@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Peter Feerick
Claude Sonnet 5
parent
abeed9f23b
commit
ed71a38726
@@ -51,6 +51,7 @@
|
||||
#define CPN_MAX_SCRIPT_OUTPUTS 6
|
||||
#define CPN_MAX_SPACEMOUSE 6
|
||||
#define CPN_MAX_INPUTS 32 // v2.10 replaces CPN_MAX_ANALOGS - the value is abitary as radio ADC refactor is still a WIP
|
||||
#define CPN_MAX_USER_DATA 50
|
||||
|
||||
// pre v2.10
|
||||
#define CPN_MAX_POTS 8
|
||||
|
||||
@@ -170,6 +170,12 @@ static const YamlLookupTable cfsSwitchLuaOverride = {
|
||||
{ 1, "ON" },
|
||||
};
|
||||
|
||||
static const YamlLookupTable udTypeLut = {
|
||||
{ 0, "INT" },
|
||||
{ 1, "FLOAT" },
|
||||
{ 2, "STRING" },
|
||||
};
|
||||
|
||||
struct YamlTrim {
|
||||
int mode = 0;
|
||||
int ref = 0;
|
||||
@@ -1307,6 +1313,14 @@ Node convert<ModelData>::encode(const ModelData& rhs)
|
||||
node["modelCustomScriptsDisabled"] = globalOnOffFilterLut << rhs.modelCustomScriptsDisabled;
|
||||
node["modelTelemetryDisabled"] = globalOnOffFilterLut << rhs.modelTelemetryDisabled;
|
||||
|
||||
for (int i=0; i<CPN_MAX_USER_DATA; i++) {
|
||||
if (!rhs.userData[i].key.empty()) {
|
||||
node["userData"][std::to_string(i)]["key"] = rhs.userData[i].key;
|
||||
node["userData"][std::to_string(i)]["type"] = udTypeLut << rhs.userData[i].type;
|
||||
node["userData"][std::to_string(i)]["value"] = rhs.userData[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -1650,6 +1664,16 @@ bool convert<ModelData>::decode(const Node& node, ModelData& rhs)
|
||||
node["modelCustomScriptsDisabled"] >> globalOnOffFilterLut >> rhs.modelCustomScriptsDisabled;
|
||||
node["modelTelemetryDisabled"] >> globalOnOffFilterLut >> rhs.modelTelemetryDisabled;
|
||||
|
||||
if (node["userData"]) {
|
||||
for (int i=0; i<CPN_MAX_USER_DATA; i++) {
|
||||
if (node["userData"][std::to_string(i)]) {
|
||||
node["userData"][std::to_string(i)]["key"] >> rhs.userData[i].key;
|
||||
node["userData"][std::to_string(i)]["type"] >> udTypeLut >> rhs.userData[i].type;
|
||||
node["userData"][std::to_string(i)]["value"] >> rhs.userData[i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preferably perform conversions here to avoid cluttering the field decodes
|
||||
|
||||
if (modelSettingsVersion < SemanticVersion("2.8.0"))
|
||||
|
||||
@@ -127,6 +127,11 @@ void ModelData::copy(const ModelData & src)
|
||||
checklistData = src.checklistData;
|
||||
updRefList = nullptr;
|
||||
memset(&updRefInfo, 0, sizeof(updRefInfo));
|
||||
for (int i = 0; i < CPN_MAX_USER_DATA; i += 1) {
|
||||
userData[i].type = src.userData[i].type;
|
||||
userData[i].key = src.userData[i].key;
|
||||
userData[i].value = src.userData[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
ExpoData * ModelData::insertInput(const int idx)
|
||||
@@ -356,6 +361,9 @@ void ModelData::clear()
|
||||
|
||||
updRefList = nullptr;
|
||||
memset(&updRefInfo, 0, sizeof(updRefInfo));
|
||||
|
||||
for (int i = 0; i < CPN_MAX_USER_DATA; i += 1)
|
||||
userData[i].clear();
|
||||
}
|
||||
|
||||
bool ModelData::isEmpty() const
|
||||
|
||||
@@ -126,6 +126,16 @@ class CustomSwitchData {
|
||||
void clear() { memset(reinterpret_cast<void *>(this), 0, sizeof(CustomSwitchData)); }
|
||||
};
|
||||
|
||||
// Key/Value data for Lua scripts
|
||||
class UserData {
|
||||
public:
|
||||
UserData() { clear(); }
|
||||
unsigned int type;
|
||||
std::string key;
|
||||
std::string value;
|
||||
void clear() { key.clear(); value.clear(); type = 0; }
|
||||
};
|
||||
|
||||
class ModelData {
|
||||
Q_DECLARE_TR_FUNCTIONS(ModelData)
|
||||
|
||||
@@ -239,6 +249,8 @@ class ModelData {
|
||||
|
||||
QByteArray checklistData;
|
||||
|
||||
UserData userData[CPN_MAX_USER_DATA];
|
||||
|
||||
ModelData & operator=(const ModelData & src);
|
||||
|
||||
void convert(RadioDataConversionState & cstate);
|
||||
|
||||
@@ -176,3 +176,107 @@ TEST_F(ModelYamlRoundTrip, AntennaModeNotWrittenWithoutExternalAntenna)
|
||||
ASSERT_TRUE(y.contains("moduleData")) << "module was not serialised";
|
||||
EXPECT_FALSE(y.contains("antennaMode"));
|
||||
}
|
||||
|
||||
// userData string values may contain embedded NUL bytes.
|
||||
TEST_F(ModelYamlRoundTrip, UserDataEmbeddedNulSurvivesRoundTrip)
|
||||
{
|
||||
ModelData m;
|
||||
m.clear();
|
||||
m.used = true;
|
||||
m.userData[0].key = "App|Key";
|
||||
m.userData[0].type = 2; // STRING
|
||||
m.userData[0].value = std::string("emb") + '\0' + "X";
|
||||
ASSERT_EQ(m.userData[0].value.size(), (size_t)5);
|
||||
|
||||
QByteArray y;
|
||||
ModelData m2 = roundTrip(m, y);
|
||||
|
||||
EXPECT_EQ(m2.userData[0].key, m.userData[0].key);
|
||||
ASSERT_EQ(m2.userData[0].value.size(), m.userData[0].value.size());
|
||||
EXPECT_EQ(m2.userData[0].value, m.userData[0].value);
|
||||
}
|
||||
|
||||
// Companion must also read a value written by the firmware's own writer,
|
||||
// which escapes non-printable bytes as \xHH - not just its own output.
|
||||
TEST_F(ModelYamlRoundTrip, UserDataParsesFirmwareEscapedEmbeddedNul)
|
||||
{
|
||||
const QByteArray yaml =
|
||||
"header:\n"
|
||||
" name: Tst\n"
|
||||
"userData:\n"
|
||||
" \"0\":\n"
|
||||
" key: \"App|Key\"\n"
|
||||
" type: STRING\n"
|
||||
" value: \"emb\\x00X\"\n";
|
||||
|
||||
ModelData m;
|
||||
m.clear();
|
||||
ASSERT_TRUE(loadModelFromYaml(m, yaml));
|
||||
|
||||
EXPECT_EQ(m.userData[0].key, "App|Key");
|
||||
const std::string expected = std::string("emb") + '\0' + "X";
|
||||
ASSERT_EQ(m.userData[0].value.size(), expected.size());
|
||||
EXPECT_EQ(m.userData[0].value, expected);
|
||||
}
|
||||
|
||||
// Quotes and backslashes are both special-cased by the firmware's writer
|
||||
// (radio/src/storage/yaml/yaml_tree_walker.cpp), more likely to show up in
|
||||
// real user data than an embedded NUL (quoted text, Windows-style paths).
|
||||
TEST_F(ModelYamlRoundTrip, UserDataEscapesQuotesAndBackslashesRoundTrip)
|
||||
{
|
||||
ModelData m;
|
||||
m.clear();
|
||||
m.used = true;
|
||||
m.userData[0].key = "App|Key";
|
||||
m.userData[0].type = 2; // STRING
|
||||
m.userData[0].value = "a\"b\\c";
|
||||
|
||||
QByteArray y;
|
||||
ModelData m2 = roundTrip(m, y);
|
||||
|
||||
EXPECT_EQ(m2.userData[0].value, m.userData[0].value);
|
||||
}
|
||||
|
||||
TEST_F(ModelYamlRoundTrip, UserDataParsesFirmwareEscapedQuotesAndBackslashes)
|
||||
{
|
||||
const QByteArray yaml =
|
||||
"header:\n"
|
||||
" name: Tst\n"
|
||||
"userData:\n"
|
||||
" \"0\":\n"
|
||||
" key: \"App|Key\"\n"
|
||||
" type: STRING\n"
|
||||
" value: \"a\\x22b\\x5Cc\"\n";
|
||||
|
||||
ModelData m;
|
||||
m.clear();
|
||||
ASSERT_TRUE(loadModelFromYaml(m, yaml));
|
||||
|
||||
EXPECT_EQ(m.userData[0].value, "a\"b\\c");
|
||||
}
|
||||
|
||||
// Multiple entries of different types survive a round trip together.
|
||||
TEST_F(ModelYamlRoundTrip, UserDataMixedTypesRoundTrip)
|
||||
{
|
||||
ModelData m;
|
||||
m.clear();
|
||||
m.used = true;
|
||||
m.userData[0].key = "App|Str";
|
||||
m.userData[0].type = 2; // STRING
|
||||
m.userData[0].value = "hello";
|
||||
m.userData[1].key = "App|Int";
|
||||
m.userData[1].type = 0; // INT
|
||||
m.userData[1].value = "12345";
|
||||
m.userData[2].key = "App|Float";
|
||||
m.userData[2].type = 1; // FLOAT
|
||||
m.userData[2].value = "1.23456789";
|
||||
|
||||
QByteArray y;
|
||||
ModelData m2 = roundTrip(m, y);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
EXPECT_EQ(m2.userData[i].key, m.userData[i].key) << "entry " << i;
|
||||
EXPECT_EQ(m2.userData[i].type, m.userData[i].type) << "entry " << i;
|
||||
EXPECT_EQ(m2.userData[i].value, m.userData[i].value) << "entry " << i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
#warning "Unknown board!"
|
||||
#endif
|
||||
|
||||
#define MAX_USER_DATA 50
|
||||
#define MAX_TIMERS 3
|
||||
#define NUM_CAL_PPM 4
|
||||
|
||||
|
||||
@@ -260,3 +260,129 @@ void ModelData::resetScreenData()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// User Data store. Each entry is a key/value pair.
|
||||
// Can be used by Lua scripts to save configuration data.
|
||||
std::vector<UserData> userData;
|
||||
|
||||
// Check if userData[n] exists - controls YAML writing
|
||||
bool ModelData::hasUserData(int n)
|
||||
{
|
||||
return (size_t)n < userData.size() && !userData[n].key.empty();
|
||||
}
|
||||
|
||||
// Get User Data item at position 'n'
|
||||
// Used when parsing YAML files
|
||||
UserData* ModelData::getUserData(int n)
|
||||
{
|
||||
if ((size_t)n >= userData.size())
|
||||
return nullptr;
|
||||
return &userData[n];
|
||||
}
|
||||
|
||||
// Get User Data item with specified key
|
||||
UserData* ModelData::getUserData(const char* key)
|
||||
{
|
||||
if (key[0]) {
|
||||
for (int i = 0; (size_t)i < userData.size(); i += 1)
|
||||
if (userData[i].key == key)
|
||||
return &userData[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get User Data item at position 'n', resizing the store if needed
|
||||
// (handles gaps in a sparse/hand-edited YAML index sequence). No
|
||||
// storageDirty() here - this also runs on plain loads, not just mutations.
|
||||
UserData* ModelData::getOrCreateUserData(int n)
|
||||
{
|
||||
if ((size_t)n >= MAX_USER_DATA) return nullptr;
|
||||
if ((size_t)n >= userData.size()) {
|
||||
userData.resize(n + 1);
|
||||
}
|
||||
return &userData[n];
|
||||
}
|
||||
|
||||
// val/val_len are used verbatim (not NUL-terminated semantics), so a
|
||||
// string value may contain embedded NUL bytes.
|
||||
static bool setUD(const char* key, const char* val, size_t val_len, UDType typ)
|
||||
{
|
||||
if (key[0] == 0) return false;
|
||||
|
||||
auto ud = g_model.getUserData(key);
|
||||
if (ud == nullptr) {
|
||||
// Reuse a gap slot (see getOrCreateUserData()) before growing the store.
|
||||
for (auto& slot : userData) {
|
||||
if (slot.key.empty()) {
|
||||
slot.key = key;
|
||||
slot.value.assign(val, val_len);
|
||||
slot.type = typ;
|
||||
storageDirty(EE_MODEL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userData.size() >= MAX_USER_DATA) return false;
|
||||
userData.emplace_back();
|
||||
userData.back().key = key;
|
||||
userData.back().value.assign(val, val_len);
|
||||
userData.back().type = typ;
|
||||
storageDirty(EE_MODEL);
|
||||
} else if (ud->type != typ || ud->value.compare(0, std::string::npos, val, val_len) != 0) {
|
||||
ud->value.assign(val, val_len);
|
||||
ud->type = typ;
|
||||
storageDirty(EE_MODEL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update or add User Data item
|
||||
bool ModelData::setUserData(const char* key, const char* str)
|
||||
{
|
||||
return setUD(key, str, strlen(str), UD_STRING);
|
||||
}
|
||||
|
||||
// Update or add User Data item - str may contain embedded NUL bytes.
|
||||
bool ModelData::setUserData(const char* key, const char* str, size_t len)
|
||||
{
|
||||
return setUD(key, str, len, UD_STRING);
|
||||
}
|
||||
|
||||
// Update or add User Data item
|
||||
bool ModelData::setUserData(const char* key, int32_t num)
|
||||
{
|
||||
std::string s = std::to_string(num);
|
||||
return setUD(key, s.c_str(), s.size(), UD_INT);
|
||||
}
|
||||
|
||||
// Update or add User Data item
|
||||
bool ModelData::setUserData(const char* key, float num)
|
||||
{
|
||||
// %.9g gives enough significant digits to round-trip a 32-bit float,
|
||||
// unlike std::to_string() which fixes 6 decimal places and silently
|
||||
// loses precision (e.g. very small or very large values).
|
||||
char buf[32];
|
||||
int len = snprintf(buf, sizeof(buf), "%.9g", (double)num);
|
||||
return setUD(key, buf, len, UD_FLOAT);
|
||||
}
|
||||
|
||||
void ModelData::deleteUserData(const char* key)
|
||||
{
|
||||
for (auto ud = userData.cbegin(); ud != userData.cend(); ud++) {
|
||||
if (ud->key == key) {
|
||||
userData.erase(ud);
|
||||
storageDirty(EE_MODEL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all User Data
|
||||
void ModelData::clearUserData()
|
||||
{
|
||||
userData.clear();
|
||||
}
|
||||
|
||||
int ModelData::getUserDataCount()
|
||||
{
|
||||
return userData.size();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
#include "quick_menu_def.h"
|
||||
#endif
|
||||
|
||||
#if !defined(BACKUP)
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
#if defined(PCBTARANIS)
|
||||
#define N_TARANIS_FIELD(x)
|
||||
#define TARANIS_FIELD(x) x;
|
||||
@@ -754,6 +758,28 @@ PACK(struct USBJoystickChData {
|
||||
#endif
|
||||
});
|
||||
|
||||
// Key/Value data for Lua scripts
|
||||
enum UDType {
|
||||
UD_INT,
|
||||
UD_FLOAT,
|
||||
UD_STRING,
|
||||
};
|
||||
struct UserData {
|
||||
#if defined(YAML_GENERATOR)
|
||||
CUST_ATTR(key, r_userdata_key, w_userdata_key);
|
||||
CUST_ATTR(type, r_userdata_type, w_userdata_type);
|
||||
CUST_ATTR(value, r_userdata_value, w_userdata_value);
|
||||
#else
|
||||
#if !defined(BACKUP)
|
||||
std::string key;
|
||||
UDType type = UD_STRING;
|
||||
std::string value;
|
||||
UserData() = default;
|
||||
UserData(const char* k, const char* v, UDType t) { key = k; value = v; type = t; }
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
PACK(struct ModelData {
|
||||
// Must match start of PartialModel
|
||||
CUST_ATTR(semver,nullptr,w_semver);
|
||||
@@ -943,6 +969,25 @@ PACK(struct ModelData {
|
||||
bool cfsGroupAlwaysOn(uint8_t n) { return bfGet<uint8_t>(cfsGroupOn, n, 1); }
|
||||
void cfsSetGroupAlwaysOn(uint8_t n, bool v) { cfsGroupOn = bfSet<uint8_t>(cfsGroupOn, v, n, 1); }
|
||||
#endif
|
||||
|
||||
#if defined(YAML_GENERATOR)
|
||||
NOBACKUP(UserData userData[MAX_USER_DATA]) FUNC(userdata_is_active);
|
||||
#endif
|
||||
bool hasUserData(int n);
|
||||
UserData* getUserData(int n);
|
||||
UserData* getUserData(const char* key);
|
||||
// Used by the YAML reader: get (creating/resizing as needed) the entry
|
||||
// at parsed array index n, in case of a sparse/non-contiguous index
|
||||
// sequence (e.g. a hand-edited or Companion-generated model file).
|
||||
UserData* getOrCreateUserData(int n);
|
||||
bool setUserData(const char* key, const char* str);
|
||||
// str may contain embedded NUL bytes.
|
||||
bool setUserData(const char* key, const char* str, size_t len);
|
||||
bool setUserData(const char* key, int32_t num);
|
||||
bool setUserData(const char* key, float num);
|
||||
void deleteUserData(const char* key);
|
||||
void clearUserData();
|
||||
int getUserDataCount();
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@@ -1963,6 +1963,187 @@ static int luaModelSetSwashRing(lua_State *L)
|
||||
}
|
||||
#endif // HELI
|
||||
|
||||
static std::string getUDKey(lua_State *L)
|
||||
{
|
||||
// generate user data key from supplied 'app' and 'key' values
|
||||
// UD key = "app|key"
|
||||
|
||||
const char* a = luaL_checkstring(L, 1);
|
||||
const char* k = luaL_checkstring(L, 2);
|
||||
|
||||
// App name and key are mandatory, and app name cannot contain '|'
|
||||
// (used internally as the app/key separator)
|
||||
if (a[0] == 0 || k[0] == 0 || strchr(a, '|') != nullptr) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string s(a);
|
||||
s += "|";
|
||||
s += k;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/*luadoc
|
||||
@function model.getUserData(app, key)
|
||||
|
||||
Get User Data value for given app + key
|
||||
|
||||
@param app (string) name of Lua app / widget / script. App name cannot contain the '|' character.
|
||||
|
||||
@param key (string) name of User Data entry
|
||||
|
||||
@retval nil no User Data value defined
|
||||
|
||||
@retval User Data value (string or number)
|
||||
|
||||
@status current Introduced in 3.0.0
|
||||
*/
|
||||
static int luaGetUserData(lua_State *L)
|
||||
{
|
||||
std::string s = getUDKey(L);
|
||||
|
||||
auto ud = g_model.getUserData(s.c_str());
|
||||
if (ud) {
|
||||
if (ud->type == UD_INT) {
|
||||
lua_pushinteger(L, strtol(ud->value.c_str(), nullptr, 10));
|
||||
} else if (ud->type == UD_FLOAT) {
|
||||
lua_pushnumber(L, strtof(ud->value.c_str(), nullptr));
|
||||
} else {
|
||||
lua_pushlstring(L, ud->value.data(), ud->value.size());
|
||||
}
|
||||
}
|
||||
else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*luadoc
|
||||
@function model.getAllUserData()
|
||||
|
||||
Get a table of all User Data entries
|
||||
|
||||
@param app (string) name of Lua app / widget / script. App name cannot contain the '|' character.
|
||||
|
||||
@retval table of all User Data entries for the named app. If the app name is not supplied returns all entries.
|
||||
|
||||
@status current Introduced in 3.0.0
|
||||
*/
|
||||
static int luaGetAllUserData(lua_State *L)
|
||||
{
|
||||
// Increases stack by pushing an empty table so lua_gettop(L) checks below must check for >= 2 (not 1).
|
||||
lua_newtable(L);
|
||||
|
||||
if (g_model.getUserDataCount() == 0) return 1;
|
||||
if (lua_gettop(L) >= 2 && lua_type(L, 1) != LUA_TSTRING) return 1;
|
||||
|
||||
bool matchApp = false;
|
||||
std::string s;
|
||||
if (lua_gettop(L) >= 2) {
|
||||
s = luaL_checkstring(L, 1);
|
||||
// App name cannot contain '|' - no entry can match, since no app
|
||||
// name stored via setUserData()/getUDKey() ever contains one either.
|
||||
if (s.find('|') != std::string::npos) return 1;
|
||||
s += "|";
|
||||
matchApp = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < g_model.getUserDataCount(); i += 1) {
|
||||
auto ud = g_model.getUserData(i);
|
||||
if (ud && !ud->key.empty()) {
|
||||
std::string k = ud->key;
|
||||
if (matchApp) {
|
||||
if (k.rfind(s, 0) != 0)
|
||||
continue;
|
||||
k = k.substr(s.size(), k.size()-s.size());
|
||||
}
|
||||
if (ud->type == UD_INT) {
|
||||
lua_pushtableinteger(L, k.c_str(), strtol(ud->value.c_str(), nullptr, 10));
|
||||
} else if (ud->type == UD_FLOAT) {
|
||||
lua_pushtablenumber(L, k.c_str(), strtof(ud->value.c_str(), nullptr));
|
||||
} else {
|
||||
// lua_pushtablestring() would truncate an embedded NUL byte.
|
||||
lua_pushstring(L, k.c_str());
|
||||
lua_pushlstring(L, ud->value.data(), ud->value.size());
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*luadoc
|
||||
@function model.setUserData(app, key, value)
|
||||
|
||||
Update User Data string for given app and key with new value.
|
||||
A new User Data entry will be created if the app + key is not found.
|
||||
|
||||
@param app (string) name of Lua app / widget / script. App name cannot contain the '|' character.
|
||||
App and key names are stored as NUL-terminated C strings, so any embedded
|
||||
'\0' character - and everything after it - will be silently truncated.
|
||||
String values may contain embedded '\0' characters, except when the value
|
||||
is both longer than 255 bytes and contains one - such a value is
|
||||
truncated at the first '\0'.
|
||||
|
||||
@param key (string) name of User Data entry
|
||||
|
||||
@param value (string or number) value to save to User Data entry
|
||||
|
||||
@retval boolean - true if user data was saved, false if save failed
|
||||
|
||||
@status current Introduced in 3.0.0
|
||||
*/
|
||||
static int luaSetUserData(lua_State *L)
|
||||
{
|
||||
if (lua_type(L, 3) != LUA_TSTRING && !lua_isnumber(L, 3)) {
|
||||
// Fallback to trigger the standard Lua type error before allocating std::string
|
||||
luaL_checkstring(L, 3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string s = getUDKey(L);
|
||||
|
||||
// App name and key are mandatory
|
||||
if (s.empty()) {
|
||||
lua_pushboolean(L, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (lua_type(L, 3) == LUA_TSTRING) {
|
||||
size_t len;
|
||||
const char* v = lua_tolstring(L, 3, &len);
|
||||
lua_pushboolean(L, g_model.setUserData(s.c_str(), v, len));
|
||||
} else if (lua_isinteger(L, 3)) {
|
||||
int32_t n = luaL_checkinteger(L, 3);
|
||||
lua_pushboolean(L, g_model.setUserData(s.c_str(), n));
|
||||
} else {
|
||||
float f = luaL_checknumber(L, 3);
|
||||
lua_pushboolean(L, g_model.setUserData(s.c_str(), f));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*luadoc
|
||||
@function model.deleteUserData(app, key)
|
||||
|
||||
Delete User Data entry for given app + key
|
||||
|
||||
@param app (string) name of Lua app / widget / script. App name cannot contain the '|' character.
|
||||
|
||||
@param key (string) name of User Data entry
|
||||
|
||||
@status current Introduced in 3.0.0
|
||||
*/
|
||||
static int luaDeleteUserData(lua_State *L)
|
||||
{
|
||||
std::string s = getUDKey(L);
|
||||
if (!s.empty())
|
||||
g_model.deleteUserData(s.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
LROT_BEGIN(modellib, NULL, 0)
|
||||
LROT_FUNCENTRY( getInfo, luaModelGetInfo )
|
||||
@@ -2008,5 +2189,9 @@ LROT_BEGIN(modellib, NULL, 0)
|
||||
LROT_FUNCENTRY( getSwashRing, luaModelGetSwashRing )
|
||||
LROT_FUNCENTRY( setSwashRing, luaModelSetSwashRing )
|
||||
#endif
|
||||
LROT_FUNCENTRY( getUserData, luaGetUserData )
|
||||
LROT_FUNCENTRY( getAllUserData, luaGetAllUserData )
|
||||
LROT_FUNCENTRY( setUserData, luaSetUserData )
|
||||
LROT_FUNCENTRY( deleteUserData, luaDeleteUserData )
|
||||
LROT_END(modellib, NULL, 0)
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ void applyDefaultTemplate()
|
||||
LayoutFactory::deleteCustomScreens();
|
||||
LayoutFactory::deleteTopBarWidgets();
|
||||
#endif
|
||||
g_model.clearUserData();
|
||||
|
||||
// enable switch warnings
|
||||
bool sw1found = false;
|
||||
|
||||
@@ -350,6 +350,7 @@ const char * readModelYaml(const char * filename, uint8_t * buffer, uint32_t siz
|
||||
#if defined(COLORLCD)
|
||||
g_model.resetScreenData();
|
||||
#endif
|
||||
g_model.clearUserData();
|
||||
auto md = reinterpret_cast<ModelData*>(buffer);
|
||||
#if defined(FLIGHT_MODES) && defined(GVARS)
|
||||
// reset GVars to default values
|
||||
|
||||
@@ -810,6 +810,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 96, struct_ModelHeader, NULL),
|
||||
@@ -879,6 +886,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -904,6 +904,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -975,6 +982,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -905,6 +905,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -976,6 +983,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -2880,3 +2880,88 @@ bool isAlwaysActive(void* user, uint8_t* data, uint32_t bitoffs)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle User Data save / load
|
||||
|
||||
bool userdata_is_active(void* user, uint8_t* data, uint32_t bitoffs)
|
||||
{
|
||||
auto tw = reinterpret_cast<YamlTreeWalker*>(user);
|
||||
return g_model.hasUserData(tw->getElmts());
|
||||
}
|
||||
|
||||
// val_len wraps for a true length > 255, but val is always NUL-terminated
|
||||
// at its real end, so strlen(val) recovers the true length whenever it's
|
||||
// >= val_len. Only a true length > 255 with an embedded NUL can still
|
||||
// truncate - a pre-existing limit of the shared uint8_t val_len param.
|
||||
static size_t userdata_val_len(const char* val, uint8_t val_len)
|
||||
{
|
||||
size_t real_len = strlen(val);
|
||||
return real_len > val_len ? real_len : val_len;
|
||||
}
|
||||
|
||||
void r_userdata_key(void* user, uint8_t* data, uint32_t bitoffs,
|
||||
const char* val, uint8_t val_len)
|
||||
{
|
||||
auto tw = reinterpret_cast<YamlTreeWalker*>(user);
|
||||
auto ud = g_model.getOrCreateUserData(tw->getElmts(1));
|
||||
if (ud) ud->key.assign(val, userdata_val_len(val, val_len));
|
||||
}
|
||||
|
||||
bool w_userdata_key(void* user, uint8_t* data, uint32_t bitoffs,
|
||||
yaml_writer_func wf, void* opaque)
|
||||
{
|
||||
auto tw = reinterpret_cast<YamlTreeWalker*>(user);
|
||||
auto ud = g_model.getUserData(tw->getElmts(1));
|
||||
if (!ud) return false;
|
||||
return yaml_output_string(ud->key.c_str(), ud->key.size(), wf, opaque, true);
|
||||
}
|
||||
|
||||
const struct YamlIdStr enum_UDType[] = {
|
||||
{ UD_INT, "INT" },
|
||||
{ UD_FLOAT, "FLOAT" },
|
||||
{ UD_STRING, "STRING" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
void r_userdata_type(void* user, uint8_t* data, uint32_t bitoffs,
|
||||
const char* val, uint8_t val_len)
|
||||
{
|
||||
auto tw = reinterpret_cast<YamlTreeWalker*>(user);
|
||||
auto ud = g_model.getUserData(tw->getElmts(1));
|
||||
if (ud) {
|
||||
ud->type = (UDType)yaml_parse_enum(enum_UDType, val, val_len);
|
||||
} else {
|
||||
TRACE("Bad model YAML - userData 'type' defined before 'key'");
|
||||
}
|
||||
}
|
||||
|
||||
bool w_userdata_type(void* user, uint8_t* data, uint32_t bitoffs,
|
||||
yaml_writer_func wf, void* opaque)
|
||||
{
|
||||
auto tw = reinterpret_cast<YamlTreeWalker*>(user);
|
||||
auto ud = g_model.getUserData(tw->getElmts(1));
|
||||
if (!ud) return false;
|
||||
const char* s = yaml_output_enum(ud->type, enum_UDType);
|
||||
return wf(opaque, s, strlen(s));
|
||||
}
|
||||
|
||||
void r_userdata_value(void* user, uint8_t* data, uint32_t bitoffs,
|
||||
const char* val, uint8_t val_len)
|
||||
{
|
||||
auto tw = reinterpret_cast<YamlTreeWalker*>(user);
|
||||
auto ud = g_model.getUserData(tw->getElmts(1));
|
||||
if (ud) {
|
||||
ud->value.assign(val, userdata_val_len(val, val_len));
|
||||
} else {
|
||||
TRACE("Bad model YAML - userData 'value' defined before 'key'");
|
||||
}
|
||||
}
|
||||
|
||||
bool w_userdata_value(void* user, uint8_t* data, uint32_t bitoffs,
|
||||
yaml_writer_func wf, void* opaque)
|
||||
{
|
||||
auto tw = reinterpret_cast<YamlTreeWalker*>(user);
|
||||
auto ud = g_model.getUserData(tw->getElmts(1));
|
||||
if (!ud) return false;
|
||||
return yaml_output_string(ud->value.c_str(), ud->value.size(), wf, opaque, true);
|
||||
}
|
||||
|
||||
@@ -848,6 +848,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 96, struct_ModelHeader, NULL),
|
||||
@@ -927,6 +934,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -943,6 +943,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1024,6 +1031,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -899,6 +899,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -970,6 +977,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -895,6 +895,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -966,6 +973,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -902,6 +902,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -974,6 +981,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -942,6 +942,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1023,6 +1030,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -902,6 +902,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -974,6 +981,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -895,6 +895,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -966,6 +973,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -942,6 +942,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1023,6 +1030,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -922,6 +922,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1001,6 +1008,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -942,6 +942,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1023,6 +1030,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -828,6 +828,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 96, struct_ModelHeader, NULL),
|
||||
@@ -905,6 +912,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -942,6 +942,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1023,6 +1030,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -828,6 +828,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 96, struct_ModelHeader, NULL),
|
||||
@@ -905,6 +912,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -943,6 +943,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1024,6 +1031,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -944,6 +944,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1025,6 +1032,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -937,6 +937,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -1018,6 +1025,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -904,6 +904,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 1048, struct_ModelHeader, NULL),
|
||||
@@ -975,6 +982,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -810,6 +810,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 192, struct_ModelHeader, NULL),
|
||||
@@ -881,6 +888,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -810,6 +810,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 192, struct_ModelHeader, NULL),
|
||||
@@ -881,6 +888,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -810,6 +810,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 192, struct_ModelHeader, NULL),
|
||||
@@ -882,6 +889,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -810,6 +810,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 96, struct_ModelHeader, NULL),
|
||||
@@ -879,6 +886,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -815,6 +815,13 @@ static const struct YamlNode struct_USBJoystickChData[] = {
|
||||
YAML_UNSIGNED( "switch_npos", 3 ),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_UserData[] = {
|
||||
YAML_IDX,
|
||||
YAML_CUSTOM("key",r_userdata_key,w_userdata_key),
|
||||
YAML_CUSTOM("type",r_userdata_type,w_userdata_type),
|
||||
YAML_CUSTOM("value",r_userdata_value,w_userdata_value),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_CUSTOM("semver",nullptr,w_semver),
|
||||
YAML_STRUCT("header", 96, struct_ModelHeader, NULL),
|
||||
@@ -884,6 +891,7 @@ static const struct YamlNode struct_ModelData[] = {
|
||||
YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable, NULL),
|
||||
YAML_ARRAY("userData", 0, 50, struct_UserData, userdata_is_active),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_PartialModel[] = {
|
||||
|
||||
@@ -222,6 +222,12 @@ YamlParser::parse(const char* buffer, unsigned int size)
|
||||
break;
|
||||
|
||||
case ps_ValEsc1:
|
||||
// Match encodings generated by Companion YAML parser
|
||||
if (*c == 'b') { scratch_buf += '\b'; state = ps_ValQuo; break; }
|
||||
if (*c == 't') { scratch_buf += '\t'; state = ps_ValQuo; break; }
|
||||
if (*c == 'n') { scratch_buf += '\n'; state = ps_ValQuo; break; }
|
||||
if (*c == 'f') { scratch_buf += '\f'; state = ps_ValQuo; break; }
|
||||
if (*c == 'r') { scratch_buf += '\r'; state = ps_ValQuo; break; }
|
||||
if (*c == 'x') {
|
||||
state = ps_ValEsc2;
|
||||
break;
|
||||
@@ -236,10 +242,13 @@ YamlParser::parse(const char* buffer, unsigned int size)
|
||||
state = ps_ValEsc3;
|
||||
break;
|
||||
}
|
||||
else if (*c >= 'A' && *c <= 'F') {
|
||||
escHexVal = (*c - 'A' + 10) << 4;
|
||||
else {
|
||||
char ch = toupper((uint8_t)*c);
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
escHexVal = (ch - 'A' + 10) << 4;
|
||||
state = ps_ValEsc3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TRACE_YAML("wrong hex digit '%c'",*c);
|
||||
return DONE_PARSING;
|
||||
@@ -251,11 +260,14 @@ YamlParser::parse(const char* buffer, unsigned int size)
|
||||
state = ps_ValQuo;
|
||||
break;
|
||||
}
|
||||
else if (*c >= 'A' && *c <= 'F') {
|
||||
escHexVal |= (*c - 'A' + 10);
|
||||
else {
|
||||
char ch = toupper((uint8_t)*c);
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
escHexVal |= (ch - 'A' + 10);
|
||||
scratch_buf += escHexVal;
|
||||
state = ps_ValQuo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TRACE_YAML("wrong hex digit '%c'",*c);
|
||||
return DONE_PARSING;
|
||||
@@ -298,7 +310,7 @@ YamlParser::parse(const char* buffer, unsigned int size)
|
||||
if ((state == ps_Val) && eof && node_found) {
|
||||
calls->set_attr(ctx, scratch_buf.c_str(), scratch_buf.size());
|
||||
}
|
||||
|
||||
|
||||
return CONTINUE_PARSING;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,14 +112,14 @@ static const char hex_digits[] {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
static bool yaml_output_string(const char* str, uint32_t max_len,
|
||||
yaml_writer_func wf, void* opaque)
|
||||
bool yaml_output_string(const char* str, uint32_t max_len,
|
||||
yaml_writer_func wf, void* opaque, bool exact)
|
||||
{
|
||||
if (!wf(opaque, "\"", 1))
|
||||
return false;
|
||||
|
||||
while(max_len > 0 && *str) {
|
||||
if (*str >= 0x20 && *str <= 0x7E && *str != '"') {
|
||||
while(max_len > 0 && (exact || *str)) {
|
||||
if (*str >= 0x20 && *str <= 0x7E && *str != '"' && *str != '\\') {
|
||||
if (!wf(opaque, str++, 1)) return false;
|
||||
max_len--;
|
||||
}
|
||||
|
||||
@@ -167,3 +167,11 @@ public:
|
||||
// utils
|
||||
uint32_t yaml_parse_enum(const struct YamlIdStr* choices, const char* val, uint8_t val_len);
|
||||
const char* yaml_output_enum(int32_t i, const struct YamlIdStr* choices);
|
||||
// exact=false (default) stops at the first NUL, matching the semantics
|
||||
// needed for a fixed-capacity char[] field whose content is shorter than
|
||||
// its declared max_len - the field is not necessarily NUL-padded so
|
||||
// anything past the terminator has no meaning. exact=true writes the full
|
||||
// max_len regardless of embedded NULs - use it when max_len already IS
|
||||
// the true content length (e.g. a std::string's size()).
|
||||
bool yaml_output_string(const char* str, uint32_t max_len, yaml_writer_func wf,
|
||||
void* opaque, bool exact = false);
|
||||
|
||||
@@ -82,6 +82,7 @@ inline void SYSTEM_RESET()
|
||||
inline void MODEL_RESET()
|
||||
{
|
||||
memset(&g_model, 0, sizeof(g_model));
|
||||
g_model.clearUserData();
|
||||
anaResetFiltered();
|
||||
extern uint8_t s_mixer_first_run_done;
|
||||
s_mixer_first_run_done = false;
|
||||
|
||||
@@ -283,4 +283,61 @@ TEST(Lua, ioSeek)
|
||||
std::filesystem::remove(simuFatfsGetRealPath("seek-test.txt"));
|
||||
}
|
||||
|
||||
// Adapted from the sample script in PR #7562 (minus the LVGL UI part):
|
||||
// populates the same app/key/value set and exercises the full Lua API
|
||||
// through the real binding layer, not just the underlying C++ store.
|
||||
TEST(Lua, testUserData)
|
||||
{
|
||||
MODEL_RESET();
|
||||
|
||||
const char userdata_tst[] =
|
||||
"model.setUserData('App', 'K1', 'String')\n"
|
||||
"model.setUserData('App', 'K2', 12345)\n"
|
||||
"model.setUserData('App', 'K3', 12.345)\n"
|
||||
|
||||
"assert(model.getUserData('App', 'K1') == 'String')\n"
|
||||
"assert(model.getUserData('App', 'K2') == 12345)\n"
|
||||
"assert(math.abs(model.getUserData('App', 'K3') - 12.345) < 0.001)\n"
|
||||
"assert(model.getUserData('Other', 'K1') == nil)\n"
|
||||
|
||||
"local ud = model.getAllUserData('App')\n"
|
||||
"local n = 0\n"
|
||||
"for k in pairs(ud) do n = n + 1 end\n"
|
||||
"assert(n == 3)\n"
|
||||
"assert(ud.K1 == 'String')\n"
|
||||
|
||||
"local all = model.getAllUserData()\n"
|
||||
"assert(all['App|K1'] == 'String')\n"
|
||||
|
||||
"model.deleteUserData('App', 'K1')\n"
|
||||
"assert(model.getUserData('App', 'K1') == nil)\n"
|
||||
"assert(model.getUserData('App', 'K2') == 12345)\n";
|
||||
|
||||
luaExecStr(userdata_tst);
|
||||
}
|
||||
|
||||
// string.char() builds the value at runtime, sidestepping the fact that
|
||||
// luaL_loadstring() (used to load this test's own Lua source) is itself
|
||||
// strlen()-based and couldn't carry a literal embedded NUL through.
|
||||
TEST(Lua, testUserDataEmbeddedNul)
|
||||
{
|
||||
MODEL_RESET();
|
||||
|
||||
const char userdata_nul_tst[] =
|
||||
"local v = string.char(65, 0, 66)\n"
|
||||
"assert(#v == 3)\n"
|
||||
"model.setUserData('App', 'NulKey', v)\n"
|
||||
|
||||
"local got = model.getUserData('App', 'NulKey')\n"
|
||||
"assert(#got == 3)\n"
|
||||
"assert(got == v)\n"
|
||||
"assert(string.byte(got, 2) == 0)\n"
|
||||
|
||||
"local all = model.getAllUserData('App')\n"
|
||||
"assert(#all.NulKey == 3)\n"
|
||||
"assert(all.NulKey == v)\n";
|
||||
|
||||
luaExecStr(userdata_nul_tst);
|
||||
}
|
||||
|
||||
#endif // #if defined(LUA)
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* Based on code named
|
||||
* opentx - https://github.com/opentx/opentx
|
||||
* th9x - http://code.google.com/p/th9x
|
||||
* er9x - http://code.google.com/p/er9x
|
||||
* gruvin9x - http://code.google.com/p/gruvin9x
|
||||
*
|
||||
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "gtests.h"
|
||||
|
||||
#include "storage/yaml/yaml_tree_walker.h"
|
||||
#include "storage/yaml/yaml_parser.h"
|
||||
#include "storage/yaml/yaml_datastructs.h"
|
||||
|
||||
class UserDataTest : public EdgeTxTest {};
|
||||
|
||||
TEST_F(UserDataTest, SetGetString)
|
||||
{
|
||||
EXPECT_TRUE(g_model.setUserData("key1", "hello"));
|
||||
auto ud = g_model.getUserData("key1");
|
||||
ASSERT_NE(ud, nullptr);
|
||||
EXPECT_EQ(ud->type, UD_STRING);
|
||||
EXPECT_EQ(ud->value, "hello");
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, SetGetInt)
|
||||
{
|
||||
EXPECT_TRUE(g_model.setUserData("key1", (int32_t)12345));
|
||||
auto ud = g_model.getUserData("key1");
|
||||
ASSERT_NE(ud, nullptr);
|
||||
EXPECT_EQ(ud->type, UD_INT);
|
||||
EXPECT_EQ(strtol(ud->value.c_str(), nullptr, 10), 12345);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, FloatRoundTrip)
|
||||
{
|
||||
// std::to_string() fixes 6 decimal places and would truncate this to
|
||||
// "0.000000" - %.9g must preserve enough precision to round-trip.
|
||||
float f = 1.234567e-5f;
|
||||
EXPECT_TRUE(g_model.setUserData("key1", f));
|
||||
auto ud = g_model.getUserData("key1");
|
||||
ASSERT_NE(ud, nullptr);
|
||||
EXPECT_EQ(ud->type, UD_FLOAT);
|
||||
EXPECT_NEAR(strtof(ud->value.c_str(), nullptr), f, f * 1e-5f);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, UpdateExistingChangesType)
|
||||
{
|
||||
// A value/type change on an existing key must persist even when the
|
||||
// string representation happens to be identical either side.
|
||||
EXPECT_TRUE(g_model.setUserData("key1", (int32_t)123));
|
||||
EXPECT_TRUE(g_model.setUserData("key1", "123"));
|
||||
auto ud = g_model.getUserData("key1");
|
||||
ASSERT_NE(ud, nullptr);
|
||||
EXPECT_EQ(ud->type, UD_STRING);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, DeleteRemovesEntry)
|
||||
{
|
||||
g_model.setUserData("key1", "a");
|
||||
g_model.deleteUserData("key1");
|
||||
EXPECT_EQ(g_model.getUserData("key1"), nullptr);
|
||||
EXPECT_EQ(g_model.getUserDataCount(), 0);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, MaxEntriesRespected)
|
||||
{
|
||||
char key[16];
|
||||
for (int i = 0; i < MAX_USER_DATA; i += 1) {
|
||||
snprintf(key, sizeof(key), "k%d", i);
|
||||
EXPECT_TRUE(g_model.setUserData(key, i));
|
||||
}
|
||||
EXPECT_FALSE(g_model.setUserData("overflow", 0));
|
||||
EXPECT_EQ(g_model.getUserDataCount(), MAX_USER_DATA);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, GetOrCreateSparseIndex)
|
||||
{
|
||||
// Simulates the YAML reader encountering a non-contiguous index
|
||||
// (e.g. a hand-edited or Companion-generated model file): the entry
|
||||
// must land at the requested index, not be appended elsewhere.
|
||||
auto ud = g_model.getOrCreateUserData(3);
|
||||
ASSERT_NE(ud, nullptr);
|
||||
ud->key = "k3";
|
||||
EXPECT_EQ(g_model.getUserDataCount(), 4);
|
||||
EXPECT_EQ(g_model.getUserData(3)->key, "k3");
|
||||
// Gap slots exist but are inactive (empty key)
|
||||
EXPECT_FALSE(g_model.hasUserData(0));
|
||||
EXPECT_TRUE(g_model.hasUserData(3));
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, GetOrCreateRespectsMax)
|
||||
{
|
||||
EXPECT_EQ(g_model.getOrCreateUserData(MAX_USER_DATA), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, SetUserDataReusesGapSlot)
|
||||
{
|
||||
// A sparse YAML load can leave the store full of inactive gap slots
|
||||
// (empty key) well before MAX_USER_DATA active entries exist; setUserData
|
||||
// must reuse a gap rather than spuriously refusing the new entry.
|
||||
auto ud = g_model.getOrCreateUserData(MAX_USER_DATA - 1);
|
||||
ASSERT_NE(ud, nullptr);
|
||||
ud->key = "last";
|
||||
EXPECT_EQ(g_model.getUserDataCount(), MAX_USER_DATA);
|
||||
|
||||
EXPECT_TRUE(g_model.setUserData("newkey", "value"));
|
||||
EXPECT_EQ(g_model.getUserDataCount(), MAX_USER_DATA);
|
||||
auto found = g_model.getUserData("newkey");
|
||||
ASSERT_NE(found, nullptr);
|
||||
EXPECT_EQ(found->value, "value");
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, SetGetEmbeddedNulString)
|
||||
{
|
||||
const char raw[] = {'a', '\0', 'b', 'c'};
|
||||
EXPECT_TRUE(g_model.setUserData("key1", raw, sizeof(raw)));
|
||||
auto ud = g_model.getUserData("key1");
|
||||
ASSERT_NE(ud, nullptr);
|
||||
EXPECT_EQ(ud->value.size(), sizeof(raw));
|
||||
EXPECT_EQ(memcmp(ud->value.data(), raw, sizeof(raw)), 0);
|
||||
}
|
||||
|
||||
static bool yaml_string_writer(void* opaque, const char* str, size_t len)
|
||||
{
|
||||
static_cast<std::string*>(opaque)->append(str, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_F(UserDataTest, YamlRoundTripPreservesEmbeddedNul)
|
||||
{
|
||||
const char raw[] = {'e', 'm', 'b', '\0', 'X'};
|
||||
ASSERT_TRUE(g_model.setUserData("nulkey", raw, sizeof(raw)));
|
||||
|
||||
// Pin the no-regression case alongside it: a value over 255 bytes with
|
||||
// no embedded NUL must still round-trip in full.
|
||||
std::string longVal(300, 'Y');
|
||||
ASSERT_TRUE(g_model.setUserData("longkey", longVal.c_str(), longVal.size()));
|
||||
|
||||
std::string yaml;
|
||||
{
|
||||
YamlTreeWalker tree;
|
||||
tree.reset(get_modeldata_nodes(), (uint8_t*)&g_model);
|
||||
ASSERT_TRUE(tree.generate(yaml_string_writer, &yaml));
|
||||
}
|
||||
|
||||
g_model.clearUserData();
|
||||
ASSERT_EQ(g_model.getUserDataCount(), 0);
|
||||
|
||||
{
|
||||
YamlTreeWalker tree;
|
||||
tree.reset(get_modeldata_nodes(), (uint8_t*)&g_model);
|
||||
YamlParser yp;
|
||||
yp.init(YamlTreeWalker::get_parser_calls(), &tree);
|
||||
yp.parse(yaml.c_str(), yaml.size());
|
||||
}
|
||||
|
||||
auto ud = g_model.getUserData("nulkey");
|
||||
ASSERT_NE(ud, nullptr);
|
||||
EXPECT_EQ(ud->value.size(), sizeof(raw));
|
||||
EXPECT_EQ(memcmp(ud->value.data(), raw, sizeof(raw)), 0);
|
||||
|
||||
auto udLong = g_model.getUserData("longkey");
|
||||
ASSERT_NE(udLong, nullptr);
|
||||
EXPECT_EQ(udLong->value, longVal);
|
||||
}
|
||||
|
||||
// Characterization test: 255 bytes is the largest value for which the
|
||||
// embedded-NUL fix is guaranteed exact (see userdata_val_len() in
|
||||
// yaml_datastructs_funcs.cpp) - pin the boundary explicitly.
|
||||
TEST_F(UserDataTest, YamlRoundTripPreserves255ByteEmbeddedNul)
|
||||
{
|
||||
std::string raw(255, 'Z');
|
||||
raw[100] = '\0';
|
||||
ASSERT_TRUE(g_model.setUserData("boundary", raw.data(), raw.size()));
|
||||
|
||||
std::string yaml;
|
||||
{
|
||||
YamlTreeWalker tree;
|
||||
tree.reset(get_modeldata_nodes(), (uint8_t*)&g_model);
|
||||
ASSERT_TRUE(tree.generate(yaml_string_writer, &yaml));
|
||||
}
|
||||
|
||||
g_model.clearUserData();
|
||||
|
||||
{
|
||||
YamlTreeWalker tree;
|
||||
tree.reset(get_modeldata_nodes(), (uint8_t*)&g_model);
|
||||
YamlParser yp;
|
||||
yp.init(YamlTreeWalker::get_parser_calls(), &tree);
|
||||
yp.parse(yaml.c_str(), yaml.size());
|
||||
}
|
||||
|
||||
auto ud = g_model.getUserData("boundary");
|
||||
ASSERT_NE(ud, nullptr);
|
||||
EXPECT_EQ(ud->value, raw);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ get_target_build_options() {
|
||||
BUILD_OPTIONS+="-DPCB=X7"
|
||||
;;
|
||||
x7access)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=ACCESS -DPXX1=YES"
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=ACCESS -DPXX1=YES -DUSE_FW_LTO=y"
|
||||
;;
|
||||
t12)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=T12 -DINTERNAL_MODULE_MULTI=ON"
|
||||
@@ -21,7 +21,7 @@ get_target_build_options() {
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=TX12"
|
||||
;;
|
||||
tx12mk2)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=TX12MK2"
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=TX12MK2 -DUSE_FW_LTO=y"
|
||||
;;
|
||||
gx12)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=GX12"
|
||||
@@ -33,10 +33,10 @@ get_target_build_options() {
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=T8"
|
||||
;;
|
||||
zorro)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=ZORRO"
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=ZORRO -DUSE_FW_LTO=y"
|
||||
;;
|
||||
pocket)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=POCKET"
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=POCKET -DUSE_FW_LTO=y"
|
||||
;;
|
||||
mt12)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=MT12"
|
||||
@@ -48,7 +48,7 @@ get_target_build_options() {
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=TPRO"
|
||||
;;
|
||||
tprov2)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=TPROV2"
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=TPROV2 -DUSE_FW_LTO=y"
|
||||
;;
|
||||
tpros)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=TPROS"
|
||||
@@ -72,7 +72,7 @@ get_target_build_options() {
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=LR3PRO"
|
||||
;;
|
||||
commando8)
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8"
|
||||
BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8 -DUSE_FW_LTO=y"
|
||||
;;
|
||||
xlite)
|
||||
BUILD_OPTIONS+="-DPCB=XLITE"
|
||||
@@ -90,7 +90,7 @@ get_target_build_options() {
|
||||
BUILD_OPTIONS+="-DPCB=X9D+ -DPCBREV=2019 -DUSE_FW_LTO=y"
|
||||
;;
|
||||
x9e)
|
||||
BUILD_OPTIONS+="-DPCB=X9E"
|
||||
BUILD_OPTIONS+="-DPCB=X9E -DUSE_FW_LTO=y"
|
||||
;;
|
||||
x10)
|
||||
BUILD_OPTIONS+="-DPCB=X10"
|
||||
|
||||
Reference in New Issue
Block a user