diff --git a/.gitignore b/.gitignore index 2a119899d3..6443bc19f8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ README.pdf # build generated files /src/main/fc/settings_generated.h /src/main/fc/settings_generated.c +/settings.json diff --git a/Makefile b/Makefile index 9f57660537..6cacdc4b33 100644 --- a/Makefile +++ b/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) diff --git a/src/main/fc/fc_msp.c b/src/main/fc/fc_msp.c index 2380b587a8..ee5894cca4 100644 --- a/src/main/fc/fc_msp.c +++ b/src/main/fc/fc_msp.c @@ -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); } diff --git a/src/main/fc/settings.c b/src/main/fc/settings.c index 790ee4828f..47d5b56823 100644 --- a/src/main/fc/settings.c +++ b/src/main/fc/settings.c @@ -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; diff --git a/src/main/fc/settings.h b/src/main/fc/settings.h index eb4e8beabf..d357e599a5 100644 --- a/src/main/fc/settings.h +++ b/src/main/fc/settings.h @@ -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. diff --git a/src/main/msp/msp_protocol_v2_common.h b/src/main/msp/msp_protocol_v2_common.h index d6d1ff6296..d7e2956a85 100644 --- a/src/main/msp/msp_protocol_v2_common.h +++ b/src/main/msp/msp_protocol_v2_common.h @@ -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)) \ No newline at end of file +#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 diff --git a/src/utils/settings.rb b/src/utils/settings.rb index 81b64eacf8..4dc971e2c5 100644 --- a/src/utils/settings.rb +++ b/src/utils/settings.rb @@ -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__} " + puts "Usage: ruby #{__FILE__} [--json ]" 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