mirror of
https://github.com/iNavFlight/inav.git
synced 2025-07-13 03:19:58 +03:00
Add support for getting and setting arbitrary settings via MSPv2
Add MSP2_COMMON_SETTING for retrieving arbitrary settings and MSP2_COMMON_SET_SETTING for setting them. This exposes any setting which was only available from the CLI via MSP now. Add support in settings.rb to generate a JSON file with all the settings, their types and their possible values for settings using a table. This file can be used by clients to properly format messages for settings over MSP.
This commit is contained in:
parent
cce3a25e35
commit
ff18b726d3
7 changed files with 232 additions and 11 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -22,3 +22,4 @@ README.pdf
|
|||
# build generated files
|
||||
/src/main/fc/settings_generated.h
|
||||
/src/main/fc/settings_generated.c
|
||||
/settings.json
|
||||
|
|
5
Makefile
5
Makefile
|
@ -939,7 +939,7 @@ CLEAN_ARTIFACTS += $(TARGET_ELF) $(TARGET_OBJS) $(TARGET_MAP)
|
|||
$(OBJECT_DIR)/$(TARGET)/build/version.o : $(TARGET_SRC)
|
||||
|
||||
# Settings generator
|
||||
.PHONY: .FORCE clean-settings
|
||||
.PHONY: .FORCE settings clean-settings
|
||||
UTILS_DIR = $(ROOT)/src/utils
|
||||
SETTINGS_GENERATOR = $(UTILS_DIR)/settings.rb
|
||||
BUILD_STAMP = $(UTILS_DIR)/build_stamp.rb
|
||||
|
@ -959,6 +959,9 @@ $(STAMP): .FORCE
|
|||
$(V1) echo "settings.yaml -> settings_generated.h, settings_generated.c" "$(STDOUT)"
|
||||
$(V1) CFLAGS="$(CFLAGS)" TARGET=$(TARGET) ruby $(SETTINGS_GENERATOR) . $(SETTINGS_FILE)
|
||||
|
||||
settings-json:
|
||||
$(V0) CFLAGS="$(CFLAGS)" TARGET=$(TARGET) ruby $(SETTINGS_GENERATOR) . $(SETTINGS_FILE) --json settings.json
|
||||
|
||||
clean-settings:
|
||||
$(V1) $(RM) $(GENERATED_SETTINGS)
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
#include "fc/rc_controls.h"
|
||||
#include "fc/rc_modes.h"
|
||||
#include "fc/runtime_config.h"
|
||||
#include "fc/settings.h"
|
||||
|
||||
#include "flight/failsafe.h"
|
||||
#include "flight/imu.h"
|
||||
|
@ -1971,6 +1972,148 @@ static mspResult_e mspFcProcessInCommand(uint16_t cmdMSP, sbuf_t *src)
|
|||
return MSP_RESULT_ACK;
|
||||
}
|
||||
|
||||
static const setting_t *mspReadSettingName(sbuf_t *src)
|
||||
{
|
||||
char name[SETTING_MAX_NAME_LENGTH];
|
||||
uint8_t c;
|
||||
size_t s = 0;
|
||||
while (1) {
|
||||
if (!sbufReadU8Safe(&c, src)) {
|
||||
return NULL;
|
||||
}
|
||||
name[s++] = c;
|
||||
if (c == '\0') {
|
||||
break;
|
||||
}
|
||||
if (s == SETTING_MAX_NAME_LENGTH) {
|
||||
// Name is too long
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return setting_find(name);
|
||||
}
|
||||
|
||||
static bool mspSettingCommand(sbuf_t *dst, sbuf_t *src)
|
||||
{
|
||||
const setting_t *setting = mspReadSettingName(src);
|
||||
if (!setting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const void *ptr = setting_get_value_pointer(setting);
|
||||
|
||||
switch (SETTING_TYPE(setting)) {
|
||||
case VAR_UINT8:
|
||||
FALLTHROUGH;
|
||||
case VAR_INT8:
|
||||
sbufWriteU8(dst, *((uint8_t*)ptr));
|
||||
break;
|
||||
case VAR_UINT16:
|
||||
FALLTHROUGH;
|
||||
break;
|
||||
case VAR_INT16:
|
||||
sbufWriteU16(dst, *((uint16_t*)ptr));
|
||||
break;
|
||||
case VAR_UINT32:
|
||||
sbufWriteU32(dst, *((uint32_t*)ptr));
|
||||
break;
|
||||
case VAR_FLOAT:
|
||||
{
|
||||
float *val = (float *)ptr;
|
||||
sbufWriteData(dst, val, sizeof(float));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mspSetSettingCommand(sbuf_t *dst, sbuf_t *src)
|
||||
{
|
||||
UNUSED(dst);
|
||||
|
||||
const setting_t *setting = mspReadSettingName(src);
|
||||
if (!setting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setting_min_t min = setting_get_min(setting);
|
||||
setting_max_t max = setting_get_max(setting);
|
||||
|
||||
void *ptr = setting_get_value_pointer(setting);
|
||||
switch (SETTING_TYPE(setting)) {
|
||||
case VAR_UINT8:
|
||||
{
|
||||
uint8_t val;
|
||||
if (!sbufReadU8Safe(&val, src)) {
|
||||
return false;
|
||||
}
|
||||
if (val > max) {
|
||||
return false;
|
||||
}
|
||||
*((uint8_t*)ptr) = val;
|
||||
}
|
||||
break;
|
||||
case VAR_INT8:
|
||||
{
|
||||
int8_t val;
|
||||
if (!sbufReadI8Safe(&val, src)) {
|
||||
return false;
|
||||
}
|
||||
if (val < min || val > (int8_t)max) {
|
||||
return false;
|
||||
}
|
||||
*((int8_t*)ptr) = val;
|
||||
}
|
||||
break;
|
||||
case VAR_UINT16:
|
||||
{
|
||||
uint16_t val;
|
||||
if (!sbufReadU16Safe(&val, src)) {
|
||||
return false;
|
||||
}
|
||||
if (val > max) {
|
||||
return false;
|
||||
}
|
||||
*((uint16_t*)ptr) = val;
|
||||
}
|
||||
break;
|
||||
case VAR_INT16:
|
||||
{
|
||||
int16_t val;
|
||||
if (!sbufReadI16Safe(&val, src)) {
|
||||
return false;
|
||||
}
|
||||
if (val < min || val > (int16_t)max) {
|
||||
return false;
|
||||
}
|
||||
*((int16_t*)ptr) = val;
|
||||
}
|
||||
break;
|
||||
case VAR_UINT32:
|
||||
{
|
||||
uint32_t val;
|
||||
if (!sbufReadU32Safe(&val, src)) {
|
||||
return false;
|
||||
}
|
||||
if (val > max) {
|
||||
return false;
|
||||
}
|
||||
*((uint32_t*)ptr) = val;
|
||||
}
|
||||
break;
|
||||
case VAR_FLOAT:
|
||||
{
|
||||
float val;
|
||||
if (!sbufReadDataSafe(src, &val, sizeof(float))) {
|
||||
return false;
|
||||
}
|
||||
*((float*)ptr) = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* Returns MSP_RESULT_ACK, MSP_RESULT_ERROR or MSP_RESULT_NO_REPLY
|
||||
*/
|
||||
|
@ -2000,6 +2143,10 @@ mspResult_e mspFcProcessCommand(mspPacket_t *cmd, mspPacket_t *reply, mspPostPro
|
|||
mspFcDataFlashReadCommand(dst, src);
|
||||
ret = MSP_RESULT_ACK;
|
||||
#endif
|
||||
} else if (cmdMSP == MSP2_COMMON_SETTING) {
|
||||
ret = mspSettingCommand(dst, src) ? MSP_RESULT_ACK : MSP_RESULT_ERROR;
|
||||
} else if (cmdMSP == MSP2_COMMON_SET_SETTING) {
|
||||
ret = mspSetSettingCommand(dst, src) ? MSP_RESULT_ACK : MSP_RESULT_ERROR;
|
||||
} else {
|
||||
ret = mspFcProcessInCommand(cmdMSP, src);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,19 @@ bool setting_name_exact_match(const setting_t *val, char *buf, const char *cmdli
|
|||
return sl_strncasecmp(cmdline, buf, strlen(buf)) == 0 && var_name_length == strlen(buf);
|
||||
}
|
||||
|
||||
const setting_t *setting_find(const char *name)
|
||||
{
|
||||
char buf[SETTING_MAX_NAME_LENGTH];
|
||||
for (int ii = 0; ii < SETTINGS_TABLE_COUNT; ii++) {
|
||||
const setting_t *setting = &settingsTable[ii];
|
||||
setting_get_name(setting, buf);
|
||||
if (strcmp(buf, name) == 0) {
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pgn_t setting_get_pgn(const setting_t *val)
|
||||
{
|
||||
uint16_t pos = val - (const setting_t *)settingsTable;
|
||||
|
|
|
@ -70,6 +70,9 @@ extern const setting_t settingsTable[];
|
|||
void setting_get_name(const setting_t *val, char *buf);
|
||||
bool setting_name_contains(const setting_t *val, char *buf, const char *cmdline);
|
||||
bool setting_name_exact_match(const setting_t *val, char *buf, const char *cmdline, uint8_t var_name_length);
|
||||
// Returns a setting_t with the exact name (case sensitive), or
|
||||
// NULL if no setting with that name exists.
|
||||
const setting_t *setting_find(const char *name);
|
||||
pgn_t setting_get_pgn(const setting_t *val);
|
||||
// Returns a pointer to the actual value stored by
|
||||
// the setting_t. The returned value might be modified.
|
||||
|
|
|
@ -16,4 +16,6 @@
|
|||
*/
|
||||
|
||||
#define MSP2_COMMON_TZ 0x1001 //out message Gets the TZ offset for the local time (returns: minutes(i16))
|
||||
#define MSP2_COMMON_SET_TZ 0x1002 //in message Sets the TZ offset for the local time (args: minutes(i16))
|
||||
#define MSP2_COMMON_SET_TZ 0x1002 //in message Sets the TZ offset for the local time (args: minutes(i16))
|
||||
#define MSP2_COMMON_SETTING 0x1003 //in/out message Returns the value for a setting
|
||||
#define MSP2_COMMON_SET_SETTING 0x1004 //in message Sets the value for a setting
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
# along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
require 'fileutils'
|
||||
require 'getoptlong'
|
||||
require 'json'
|
||||
require 'set'
|
||||
require 'stringio'
|
||||
require 'tmpdir'
|
||||
|
@ -281,10 +283,8 @@ class Generator
|
|||
end
|
||||
end
|
||||
|
||||
@data = YAML.load_file(@settings_file)
|
||||
load_data
|
||||
|
||||
initialize_tables
|
||||
check_conditions
|
||||
sanitize_fields
|
||||
initialize_name_encoder
|
||||
initialize_value_encoder
|
||||
|
@ -293,6 +293,29 @@ class Generator
|
|||
write_impl_file(impl_file)
|
||||
end
|
||||
|
||||
def write_json(jsonFile)
|
||||
load_data
|
||||
sanitize_fields(true)
|
||||
|
||||
settings = Hash.new
|
||||
|
||||
foreach_member do |group, member|
|
||||
name = member["name"]
|
||||
s = {
|
||||
"type" => member["type"],
|
||||
}
|
||||
table = member["table"]
|
||||
if table
|
||||
s["table"] = @tables[table]
|
||||
end
|
||||
settings[name] = s
|
||||
end
|
||||
|
||||
File.open(jsonFile, "w") do |f|
|
||||
f.write(JSON.pretty_generate(settings))
|
||||
end
|
||||
end
|
||||
|
||||
def print_stats
|
||||
puts "#{@count} settings"
|
||||
puts "words table has #{@name_encoder.words.length} words"
|
||||
|
@ -316,6 +339,13 @@ class Generator
|
|||
|
||||
private
|
||||
|
||||
def load_data
|
||||
@data = YAML.load_file(@settings_file)
|
||||
|
||||
initialize_tables
|
||||
check_conditions
|
||||
end
|
||||
|
||||
def header_file
|
||||
File.join(@output_dir, "settings_generated.h")
|
||||
end
|
||||
|
@ -651,11 +681,11 @@ class Generator
|
|||
end
|
||||
end
|
||||
|
||||
def sanitize_fields
|
||||
def sanitize_fields(all=false)
|
||||
pending_types = Hash.new
|
||||
has_booleans = false
|
||||
|
||||
foreach_enabled_member do |group, member|
|
||||
block = Proc.new do |group, member|
|
||||
if !group["name"]
|
||||
raise "Missing group name"
|
||||
end
|
||||
|
@ -682,6 +712,8 @@ class Generator
|
|||
end
|
||||
end
|
||||
|
||||
all ? foreach_member(&block) : foreach_enabled_member(&block)
|
||||
|
||||
if has_booleans
|
||||
@tables[OFF_ON_TABLE["name"]] = OFF_ON_TABLE
|
||||
@used_tables << OFF_ON_TABLE["name"]
|
||||
|
@ -836,7 +868,7 @@ class Generator
|
|||
end
|
||||
|
||||
def usage
|
||||
puts "Usage: ruby #{__FILE__} <source_dir> <settings_file>"
|
||||
puts "Usage: ruby #{__FILE__} <source_dir> <settings_file> [--json <json_file>]"
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
|
@ -851,10 +883,30 @@ if __FILE__ == $0
|
|||
end
|
||||
|
||||
gen = Generator.new(src_root, settings_file)
|
||||
gen.write_files()
|
||||
|
||||
if verbose
|
||||
gen.print_stats()
|
||||
opts = GetoptLong.new(
|
||||
[ "--help", "-h", GetoptLong::NO_ARGUMENT ],
|
||||
[ "--json", "-j", GetoptLong::REQUIRED_ARGUMENT ],
|
||||
)
|
||||
|
||||
jsonFile = nil
|
||||
|
||||
opts.each do |opt, arg|
|
||||
case opt
|
||||
when "--help"
|
||||
usage()
|
||||
exit(0)
|
||||
when "--json"
|
||||
jsonFile = arg
|
||||
end
|
||||
end
|
||||
|
||||
if jsonFile
|
||||
gen.write_json(jsonFile)
|
||||
else
|
||||
gen.write_files()
|
||||
if verbose
|
||||
gen.print_stats()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue