diff --git a/DEFAULT_LICENCE.md b/DEFAULT_LICENSE.md
similarity index 100%
rename from DEFAULT_LICENCE.md
rename to DEFAULT_LICENSE.md
diff --git a/mk/source.mk b/mk/source.mk
index b4e9b07aabe..6ab52f66e3b 100644
--- a/mk/source.mk
+++ b/mk/source.mk
@@ -64,6 +64,7 @@ COMMON_SRC = \
io/usb_msc.c \
msp/msp.c \
msp/msp_box.c \
+ msp/msp_build_info.c \
msp/msp_serial.c \
scheduler/scheduler.c \
sensors/adcinternal.c \
diff --git a/src/main/msp/msp.c b/src/main/msp/msp.c
index 5c8f944611c..70df7a07dea 100644
--- a/src/main/msp/msp.c
+++ b/src/main/msp/msp.c
@@ -107,6 +107,7 @@
#include "io/vtx_msp.h"
#include "msp/msp_box.h"
+#include "msp/msp_build_info.h"
#include "msp/msp_protocol.h"
#include "msp/msp_protocol_v2_betaflight.h"
#include "msp/msp_protocol_v2_common.h"
@@ -749,6 +750,8 @@ static bool mspCommonProcessOutCommand(int16_t cmdMSP, sbuf_t *dst, mspPostProce
sbufWriteData(dst, buildDate, BUILD_DATE_LENGTH);
sbufWriteData(dst, buildTime, BUILD_TIME_LENGTH);
sbufWriteData(dst, shortGitRevision, GIT_SHORT_REVISION_LENGTH);
+ // Added in API version 1.47
+ sbufWriteBuildInfoFlags(dst);
break;
case MSP_ANALOG:
diff --git a/src/main/msp/msp_build_info.c b/src/main/msp/msp_build_info.c
new file mode 100644
index 00000000000..4fb0b421124
--- /dev/null
+++ b/src/main/msp/msp_build_info.c
@@ -0,0 +1,176 @@
+/*
+ * This file is part of Betaflight.
+ *
+ * Betaflight is free software. You can redistribute this software
+ * and/or modify this software under the terms of the GNU General
+ * Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * Betaflight 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this software.
+ *
+ * If not, see .
+ */
+
+/*
+ * WARNING: This is an auto-generated file, please do not edit directly!
+ *
+ * Generator : `src/utils/make-build-info.py`
+ * Source : https://build.betaflight.com/api/options/4.5.0-zulu
+ * Input hash : 2b66ff01549c4d858593dbf96c6aae12
+ */
+
+#include
+
+#include "platform.h"
+
+#include "common/streambuf.h"
+
+#include "msp/msp_build_info.h"
+
+void sbufWriteBuildInfoFlags(sbuf_t *dst)
+{
+ static const uint16_t options[] = {
+#ifdef USE_SERIALRX_CRSF
+ BUILD_OPTION_SERIALRX_CRSF,
+#endif
+#ifdef USE_SERIALRX_FPORT
+ BUILD_OPTION_SERIALRX_FPORT,
+#endif
+#ifdef USE_SERIALRX_GHST
+ BUILD_OPTION_SERIALRX_GHST,
+#endif
+#ifdef USE_SERIALRX_IBUS
+ BUILD_OPTION_SERIALRX_IBUS,
+#endif
+#ifdef USE_SERIALRX_JETIEXBUS
+ BUILD_OPTION_SERIALRX_JETIEXBUS,
+#endif
+#ifdef USE_RX_PPM
+ BUILD_OPTION_RX_PPM,
+#endif
+#ifdef USE_SERIALRX_SBUS
+ BUILD_OPTION_SERIALRX_SBUS,
+#endif
+#ifdef USE_SERIALRX_SPEKTRUM
+ BUILD_OPTION_SERIALRX_SPEKTRUM,
+#endif
+#ifdef USE_SERIALRX_SRXL2
+ BUILD_OPTION_SERIALRX_SRXL2,
+#endif
+#ifdef USE_SERIALRX_SUMD
+ BUILD_OPTION_SERIALRX_SUMD,
+#endif
+#ifdef USE_SERIALRX_SUMH
+ BUILD_OPTION_SERIALRX_SUMH,
+#endif
+#ifdef USE_SERIALRX_XBUS
+ BUILD_OPTION_SERIALRX_XBUS,
+#endif
+#ifdef USE_TELEMETRY_FRSKY_HUB
+ BUILD_OPTION_TELEMETRY_FRSKY_HUB,
+#endif
+#ifdef USE_TELEMETRY_HOTT
+ BUILD_OPTION_TELEMETRY_HOTT,
+#endif
+#ifdef USE_TELEMETRY_IBUS_EXTENDED
+ BUILD_OPTION_TELEMETRY_IBUS_EXTENDED,
+#endif
+#ifdef USE_TELEMETRY_LTM
+ BUILD_OPTION_TELEMETRY_LTM,
+#endif
+#ifdef USE_TELEMETRY_MAVLINK
+ BUILD_OPTION_TELEMETRY_MAVLINK,
+#endif
+#ifdef USE_TELEMETRY_SMARTPORT
+ BUILD_OPTION_TELEMETRY_SMARTPORT,
+#endif
+#ifdef USE_TELEMETRY_SRXL
+ BUILD_OPTION_TELEMETRY_SRXL,
+#endif
+#ifdef USE_ACRO_TRAINER
+ BUILD_OPTION_ACRO_TRAINER,
+#endif
+#ifdef USE_AKK_SMARTAUDIO
+ BUILD_OPTION_AKK_SMARTAUDIO,
+#endif
+#ifdef USE_BATTERY_CONTINUE
+ BUILD_OPTION_BATTERY_CONTINUE,
+#endif
+#ifdef USE_CAMERA_CONTROL
+ BUILD_OPTION_CAMERA_CONTROL,
+#endif
+#ifdef USE_DASHBOARD
+ BUILD_OPTION_DASHBOARD,
+#endif
+#ifdef USE_EMFAT_TOOLS
+ BUILD_OPTION_EMFAT_TOOLS,
+#endif
+#ifdef USE_ESCSERIAL_SIMONK
+ BUILD_OPTION_ESCSERIAL_SIMONK,
+#endif
+#ifdef USE_FRSKYOSD
+ BUILD_OPTION_FRSKYOSD,
+#endif
+#ifdef USE_GPS
+ BUILD_OPTION_GPS,
+#endif
+#ifdef USE_LED_STRIP
+ BUILD_OPTION_LED_STRIP,
+#endif
+#ifdef USE_LED_STRIP_64
+ BUILD_OPTION_LED_STRIP_64,
+#endif
+#ifdef USE_MAG
+ BUILD_OPTION_MAG,
+#endif
+#ifdef USE_OSD_SD
+ BUILD_OPTION_OSD_SD,
+#endif
+#ifdef USE_OSD_HD
+ BUILD_OPTION_OSD_HD,
+#endif
+#ifdef USE_PINIO
+ BUILD_OPTION_PINIO,
+#endif
+#ifdef USE_RACE_PRO
+ BUILD_OPTION_RACE_PRO,
+#endif
+#ifdef USE_SERVOS
+ BUILD_OPTION_SERVOS,
+#endif
+#ifdef USE_VTX
+ BUILD_OPTION_VTX,
+#endif
+#ifdef USE_BRUSHED
+ BUILD_OPTION_BRUSHED,
+#endif
+#ifdef USE_DSHOT
+ BUILD_OPTION_DSHOT,
+#endif
+#ifdef USE_MULTISHOT
+ BUILD_OPTION_MULTISHOT,
+#endif
+#ifdef USE_ONESHOT
+ BUILD_OPTION_ONESHOT,
+#endif
+#ifdef USE_PROSHOT
+ BUILD_OPTION_PROSHOT,
+#endif
+#ifdef USE_PWM_OUTPUT
+ BUILD_OPTION_PWM_OUTPUT,
+#endif
+ };
+
+ for (unsigned i = 0; i < ARRAYLEN(options); i++)
+ {
+ sbufWriteU16(dst, options[i]);
+ }
+}
diff --git a/src/main/msp/msp_build_info.h b/src/main/msp/msp_build_info.h
new file mode 100644
index 00000000000..0690b7b4c24
--- /dev/null
+++ b/src/main/msp/msp_build_info.h
@@ -0,0 +1,82 @@
+/*
+ * This file is part of Betaflight.
+ *
+ * Betaflight is free software. You can redistribute this software
+ * and/or modify this software under the terms of the GNU General
+ * Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * Betaflight 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this software.
+ *
+ * If not, see .
+ */
+
+/*
+ * WARNING: This is an auto-generated file, please do not edit directly!
+ *
+ * Generator : `src/utils/make-build-info.py`
+ * Source : https://build.betaflight.com/api/options/4.5.0-zulu
+ * Input hash : 2b66ff01549c4d858593dbf96c6aae12
+ */
+
+#pragma once
+
+#include "common/streambuf.h"
+
+// Radio Protocols
+#define BUILD_OPTION_SERIALRX_CRSF 4097
+#define BUILD_OPTION_SERIALRX_FPORT 4098
+#define BUILD_OPTION_SERIALRX_GHST 4099
+#define BUILD_OPTION_SERIALRX_IBUS 4100
+#define BUILD_OPTION_SERIALRX_JETIEXBUS 4101
+#define BUILD_OPTION_RX_PPM 4102
+#define BUILD_OPTION_SERIALRX_SBUS 4103
+#define BUILD_OPTION_SERIALRX_SPEKTRUM 4104
+#define BUILD_OPTION_SERIALRX_SRXL2 4105
+#define BUILD_OPTION_SERIALRX_SUMD 4106
+#define BUILD_OPTION_SERIALRX_SUMH 4107
+#define BUILD_OPTION_SERIALRX_XBUS 4108
+// Telemetry Protocols
+#define BUILD_OPTION_TELEMETRY_FRSKY_HUB 12301
+#define BUILD_OPTION_TELEMETRY_HOTT 12302
+#define BUILD_OPTION_TELEMETRY_IBUS_EXTENDED 12303
+#define BUILD_OPTION_TELEMETRY_LTM 12304
+#define BUILD_OPTION_TELEMETRY_MAVLINK 12305
+#define BUILD_OPTION_TELEMETRY_SMARTPORT 12306
+#define BUILD_OPTION_TELEMETRY_SRXL 12307
+// General Options
+#define BUILD_OPTION_ACRO_TRAINER 16404
+#define BUILD_OPTION_AKK_SMARTAUDIO 16405
+#define BUILD_OPTION_BATTERY_CONTINUE 16406
+#define BUILD_OPTION_CAMERA_CONTROL 16407
+#define BUILD_OPTION_DASHBOARD 16408
+#define BUILD_OPTION_EMFAT_TOOLS 16409
+#define BUILD_OPTION_ESCSERIAL_SIMONK 16410
+#define BUILD_OPTION_FRSKYOSD 16411
+#define BUILD_OPTION_GPS 16412
+#define BUILD_OPTION_LED_STRIP 16413
+#define BUILD_OPTION_LED_STRIP_64 16414
+#define BUILD_OPTION_MAG 16415
+#define BUILD_OPTION_OSD_SD 16416
+#define BUILD_OPTION_OSD_HD 16417
+#define BUILD_OPTION_PINIO 16418
+#define BUILD_OPTION_RACE_PRO 16419
+#define BUILD_OPTION_SERVOS 16420
+#define BUILD_OPTION_VTX 16421
+// Motor Protocols
+#define BUILD_OPTION_BRUSHED 8230
+#define BUILD_OPTION_DSHOT 8231
+#define BUILD_OPTION_MULTISHOT 8232
+#define BUILD_OPTION_ONESHOT 8233
+#define BUILD_OPTION_PROSHOT 8234
+#define BUILD_OPTION_PWM_OUTPUT 8235
+
+void sbufWriteBuildInfoFlags(sbuf_t *dst);
diff --git a/src/utils/make-build-info.py b/src/utils/make-build-info.py
new file mode 100755
index 00000000000..fcb468b1249
--- /dev/null
+++ b/src/utils/make-build-info.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
+from hashlib import md5
+import json
+import logging
+import os
+
+import requests
+
+
+HEADER_FILE_TEMPLATE = """{license_header}
+
+{generated_warning}
+
+#pragma once
+
+#include "common/streambuf.h"
+
+{defines}
+
+void sbufWriteBuildInfoFlags(sbuf_t *dst);
+"""
+
+SOURCE_FILE_TEMPLATE = """{license_header}
+
+{generated_warning}
+
+#include
+
+#include "platform.h"
+
+#include "common/streambuf.h"
+
+#include "msp/msp_build_info.h"
+
+void sbufWriteBuildInfoFlags(sbuf_t *dst)
+{
+ static const uint16_t options[] = {
+{build_options}
+ };
+
+ for (unsigned i = 0; i < ARRAYLEN(options); i++)
+ {
+ sbufWriteU16(dst, options[i]);
+ }
+}
+"""
+
+WARNING_COMMENT_TEMPLATE = """
+/*
+ * WARNING: This is an auto-generated file, please do not edit directly!
+ *
+ * Generator : `src/utils/make-build-info.py`
+ * Source : {source}
+ * Input hash : {input_hash}
+ */
+"""
+
+
+def __find_project_root() -> str:
+ utils_dir = os.path.abspath(os.path.dirname(__file__))
+ src_dir = os.path.dirname(utils_dir)
+ root_dir = os.path.dirname(src_dir)
+ return os.path.realpath(root_dir)
+
+
+def camel_case_to_title(s: str) -> str:
+ if not s:
+ return "Unspecified"
+ else:
+ spaceless = s.replace(" ", "")
+ return "".join([(c if not c.isupper() else f" {c}") for c in spaceless]) \
+ .lstrip() \
+ .title()
+
+
+def fetch_build_options(endpoint_url: str) -> tuple:
+ logging.info(f"Fetching JSON: {endpoint_url}")
+ data = requests.get(endpoint_url, timeout=2).json()
+ input_hash = md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
+ logging.info(f"Input hash: {input_hash}")
+
+ defines = []
+ options = []
+ groups = list(data.keys())
+ for group_index, option_list in enumerate(data.values()):
+ for option in option_list:
+ define = option.get("value")
+ if define:
+ defines.append(define)
+ number = option.get("key")
+ name = define.replace("USE_", "BUILD_OPTION_")
+ options.append((name, number, camel_case_to_title(groups[group_index])))
+ logging.info(f"Number of defines: {len(defines)}")
+ return defines, options, input_hash
+
+
+def get_warning_comment(source: str, input_hash: str) -> str:
+ return WARNING_COMMENT_TEMPLATE \
+ .strip() \
+ .replace("{source}", source) \
+ .replace("{input_hash}", input_hash) \
+
+
+def main(root_path: str, target_path: str, endpoint_url: str):
+ logging.info(f"Project root: {root_path}")
+
+ license_file_path = os.path.join(root_path, "DEFAULT_LICENSE.md")
+ msp_build_info_c_path = os.path.join(target_path, "msp_build_info.c")
+ msp_build_info_h_path = os.path.join(target_path, "msp_build_info.h")
+
+ with open(license_file_path) as f:
+ license_header = f.read().rstrip()
+
+ gates, options, input_hash = fetch_build_options(endpoint_url)
+
+ generated_warning = get_warning_comment(endpoint_url, input_hash)
+
+ with open(msp_build_info_h_path, "w+") as f:
+ max_len = max(map(lambda x: len(x[0]), options)) + 4
+ lines = []
+ last_group = None
+ for option_name, option_value, group in options:
+ if group != last_group:
+ lines.append(f"// {group}")
+ last_group = group
+ lines.append(f"#define {option_name:<{max_len}}{option_value}")
+ data = HEADER_FILE_TEMPLATE \
+ .replace("{license_header}", license_header) \
+ .replace("{generated_warning}", generated_warning) \
+ .replace("{defines}", "\n".join(lines))
+ f.write(data)
+
+ logging.info(f"Written header file: {msp_build_info_h_path}")
+
+ with open(msp_build_info_c_path, "w+") as f:
+ lines = []
+ indent = " " * 8
+ for i, define in enumerate(gates):
+ option_name, _, _ = options[i]
+ lines.append(f"#ifdef {define}")
+ lines.append(f"{indent}{option_name},")
+ lines.append("#endif")
+ data = SOURCE_FILE_TEMPLATE \
+ .replace("{license_header}", license_header) \
+ .replace("{generated_warning}", generated_warning) \
+ .replace("{build_options}", "\n".join(lines))
+ f.write(data)
+
+ logging.info(f"Written source file: {msp_build_info_c_path}")
+
+
+if __name__ == "__main__":
+ PROJECT_ROOT_DIR = __find_project_root()
+ DEFAULT_TARGET_DIR = os.path.join(PROJECT_ROOT_DIR, "src", "main", "msp")
+
+ parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
+ parser.add_argument("endpoint_url", help="URL to build options API endpoint")
+ parser.add_argument("-d", "--target-dir", default=DEFAULT_TARGET_DIR, help="Path to output directory")
+ parser.add_argument("-v", "--verbose", action="store_true")
+
+ args = parser.parse_args()
+
+ logging.basicConfig(level=logging.INFO if args.verbose else logging.ERROR)
+
+ main(
+ root_path=PROJECT_ROOT_DIR,
+ target_path=args.target_dir,
+ endpoint_url=args.endpoint_url,
+ )