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:
philmoz
2026-09-18 09:04:48 +10:00
committed by GitHub
co-authored by Peter Feerick Claude Sonnet 5
parent abeed9f23b
commit ed71a38726
45 changed files with 1105 additions and 16 deletions
+185
View File
@@ -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)
}