Browse Source

✨ Configurations embed and retrieve (#21321)

X-Ryl669 2 years ago
parent
commit
b464a4b1a4
No account linked to committer's email address

+ 3
- 0
.gitignore View File

@@ -22,6 +22,9 @@
22 22
 # Generated files
23 23
 _Version.h
24 24
 bdf2u8g
25
+marlin_config.json
26
+mczip.h
27
+*.gen
25 28
 
26 29
 #
27 30
 # OS

+ 8
- 0
Marlin/Configuration_adv.h View File

@@ -1593,6 +1593,14 @@
1593 1593
     #define SD_FIRMWARE_UPDATE_INACTIVE_VALUE 0xFF
1594 1594
   #endif
1595 1595
 
1596
+  /**
1597
+   * Enable this option if you have more than ~3K of unused flash space.
1598
+   * Marlin will embed all settings in the firmware binary as compressed data.
1599
+   * Use 'M503 C' to write the settings out to the SD Card as 'mc.zip'.
1600
+   * See docs/ConfigEmbedding.md for details on how to use 'mc-apply.py'.
1601
+   */
1602
+  //#define CONFIGURATION_EMBEDDING
1603
+
1596 1604
   // Add an optimized binary file transfer mode, initiated with 'M28 B1'
1597 1605
   //#define BINARY_FILE_TRANSFER
1598 1606
 

+ 18
- 0
Marlin/src/gcode/eeprom/M500-M504.cpp View File

@@ -25,6 +25,11 @@
25 25
 #include "../../core/serial.h"
26 26
 #include "../../inc/MarlinConfig.h"
27 27
 
28
+#if ENABLED(CONFIGURATION_EMBEDDING)
29
+  #include "../../sd/SdBaseFile.h"
30
+  #include "../../mczip.h"
31
+#endif
32
+
28 33
 /**
29 34
  * M500: Store settings in EEPROM
30 35
  */
@@ -50,9 +55,22 @@ void GcodeSuite::M502() {
50 55
 
51 56
   /**
52 57
    * M503: print settings currently in memory
58
+   *
59
+   * With CONFIGURATION_EMBEDDING:
60
+   *   C<flag> : Save the full Marlin configuration to SD Card as "mc.zip"
53 61
    */
54 62
   void GcodeSuite::M503() {
55 63
     (void)settings.report(!parser.boolval('S', true));
64
+
65
+    #if ENABLED(CONFIGURATION_EMBEDDING)
66
+      if (parser.seen_test('C')) {
67
+        SdBaseFile file;
68
+        const uint16_t size = sizeof(mc_zip);
69
+        // Need to create the config size on the SD card
70
+        if (file.open("mc.zip", O_WRITE|O_CREAT) && file.write(pgm_read_ptr(mc_zip), size) != -1 && file.close())
71
+          SERIAL_ECHO_MSG("Configuration saved as 'mc.zip'");
72
+      }
73
+    #endif
56 74
   }
57 75
 
58 76
 #endif // !DISABLE_M503

+ 21
- 7
Marlin/src/gcode/host/M115.cpp View File

@@ -24,7 +24,6 @@
24 24
 #include "../../inc/MarlinConfig.h"
25 25
 #include "../queue.h"           // for getting the command port
26 26
 
27
-
28 27
 #if ENABLED(M115_GEOMETRY_REPORT)
29 28
   #include "../../module/motion.h"
30 29
 #endif
@@ -33,13 +32,25 @@
33 32
   #include "../../feature/caselight.h"
34 33
 #endif
35 34
 
35
+//#define MINIMAL_CAP_LINES // Don't even mention the disabled capabilities
36
+
36 37
 #if ENABLED(EXTENDED_CAPABILITIES_REPORT)
37
-  static void cap_line(FSTR_P const name, bool ena=false) {
38
-    SERIAL_ECHOPGM("Cap:");
39
-    SERIAL_ECHOF(name);
40
-    SERIAL_CHAR(':', '0' + ena);
41
-    SERIAL_EOL();
42
-  }
38
+  #if ENABLED(MINIMAL_CAP_LINES)
39
+    #define cap_line(S,C) if (C) _cap_line(S)
40
+    static void _cap_line(FSTR_P const name) {
41
+      SERIAL_ECHOPGM("Cap:");
42
+      SERIAL_ECHOF(name);
43
+      SERIAL_ECHOLNPGM(":1");
44
+    }
45
+  #else
46
+    #define cap_line(V...) _cap_line(V)
47
+    static void _cap_line(FSTR_P const name, bool ena=false) {
48
+      SERIAL_ECHOPGM("Cap:");
49
+      SERIAL_ECHOF(name);
50
+      SERIAL_CHAR(':', '0' + ena);
51
+      SERIAL_EOL();
52
+    }
53
+  #endif
43 54
 #endif
44 55
 
45 56
 /**
@@ -167,6 +178,9 @@ void GcodeSuite::M115() {
167 178
     // MEATPACK Compression
168 179
     cap_line(F("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack));
169 180
 
181
+    // CONFIG_EXPORT
182
+    cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIG_EMBED_AND_SAVE_TO_SD));
183
+
170 184
     // Machine Geometry
171 185
     #if ENABLED(M115_GEOMETRY_REPORT)
172 186
       const xyz_pos_t bmin = { 0, 0, 0 },

+ 6
- 0
Marlin/src/inc/Conditionals_adv.h View File

@@ -1004,3 +1004,9 @@
1004 1004
 #if EITHER(MEATPACK_ON_SERIAL_PORT_1, MEATPACK_ON_SERIAL_PORT_2)
1005 1005
   #define HAS_MEATPACK 1
1006 1006
 #endif
1007
+
1008
+// AVR are (usually) too limited in resources to store the configuration into the binary
1009
+#if !defined(FORCE_CONFIG_EMBED) && (defined(__AVR__) || DISABLED(SDSUPPORT) || EITHER(SDCARD_READONLY, DISABLE_M503))
1010
+  #undef CONFIGURATION_EMBEDDING
1011
+  #define CANNOT_EMBED_CONFIGURATION defined(__AVR__)
1012
+#endif

+ 4
- 0
Marlin/src/inc/Warnings.cpp View File

@@ -549,3 +549,7 @@
549 549
 #elif !USE_SENSORLESS && ENABLED(USES_DIAG_PINS)
550 550
   #warning "Driver DIAG pins must be physically removed unless SENSORLESS_HOMING is enabled. (See https://bit.ly/2ZPRlt0)"
551 551
 #endif
552
+
553
+#if CANNOT_EMBED_CONFIGURATION
554
+  #warning "Disabled CONFIGURATION_EMBEDDING because the target usually has less flash storage. Define FORCE_CONFIG_EMBED to override."
555
+#endif

+ 1
- 1
buildroot/bin/restore_configs View File

@@ -2,4 +2,4 @@
2 2
 
3 3
 git checkout Marlin/Configuration*.h 2>/dev/null
4 4
 git checkout Marlin/src/pins/ramps/pins_RAMPS.h 2>/dev/null
5
-rm -f Marlin/_Bootscreen.h Marlin/_Statusscreen.h
5
+rm -f Marlin/_Bootscreen.h Marlin/_Statusscreen.h marlin_config.json .pio/build/mc.zip

+ 0
- 1
buildroot/share/PlatformIO/scripts/SAMD51_grandcentral_m4.py View File

@@ -6,7 +6,6 @@ import pioutil
6 6
 if pioutil.is_pio_build():
7 7
 	from os.path import join, isfile
8 8
 	import shutil
9
-	from pprint import pprint
10 9
 
11 10
 	Import("env")
12 11
 

+ 0
- 0
buildroot/share/PlatformIO/scripts/__init__.py View File


+ 11
- 76
buildroot/share/PlatformIO/scripts/common-dependencies.py View File

@@ -193,63 +193,6 @@ if pioutil.is_pio_build():
193 193
 				set_env_field('lib_ignore', lib_ignore)
194 194
 
195 195
 	#
196
-	# Find a compiler, considering the OS
197
-	#
198
-	ENV_BUILD_PATH = os.path.join(env.Dictionary('PROJECT_BUILD_DIR'), env['PIOENV'])
199
-	GCC_PATH_CACHE = os.path.join(ENV_BUILD_PATH, ".gcc_path")
200
-	def search_compiler():
201
-		try:
202
-			filepath = env.GetProjectOption('custom_gcc')
203
-			blab("Getting compiler from env")
204
-			return filepath
205
-		except:
206
-			pass
207
-
208
-		if os.path.exists(GCC_PATH_CACHE):
209
-			with open(GCC_PATH_CACHE, 'r') as f:
210
-				return f.read()
211
-
212
-		# Find the current platform compiler by searching the $PATH
213
-		# which will be in a platformio toolchain bin folder
214
-		path_regex = re.escape(env['PROJECT_PACKAGES_DIR'])
215
-
216
-		# See if the environment provides a default compiler
217
-		try:
218
-			gcc = env.GetProjectOption('custom_deps_gcc')
219
-		except:
220
-			gcc = "g++"
221
-
222
-		if env['PLATFORM'] == 'win32':
223
-			path_separator = ';'
224
-			path_regex += r'.*\\bin'
225
-			gcc += ".exe"
226
-		else:
227
-			path_separator = ':'
228
-			path_regex += r'/.+/bin'
229
-
230
-		# Search for the compiler
231
-		for pathdir in env['ENV']['PATH'].split(path_separator):
232
-			if not re.search(path_regex, pathdir, re.IGNORECASE):
233
-				continue
234
-			for filepath in os.listdir(pathdir):
235
-				if not filepath.endswith(gcc):
236
-					continue
237
-				# Use entire path to not rely on env PATH
238
-				filepath = os.path.sep.join([pathdir, filepath])
239
-				# Cache the g++ path to no search always
240
-				if os.path.exists(ENV_BUILD_PATH):
241
-					with open(GCC_PATH_CACHE, 'w+') as f:
242
-						f.write(filepath)
243
-
244
-				return filepath
245
-
246
-		filepath = env.get('CXX')
247
-		if filepath == 'CC':
248
-			filepath = gcc
249
-		blab("Couldn't find a compiler! Fallback to %s" % filepath)
250
-		return filepath
251
-
252
-	#
253 196
 	# Use the compiler to get a list of all enabled features
254 197
 	#
255 198
 	def load_marlin_features():
@@ -257,25 +200,8 @@ if pioutil.is_pio_build():
257 200
 			return
258 201
 
259 202
 		# Process defines
260
-		build_flags = env.get('BUILD_FLAGS')
261
-		build_flags = env.ParseFlagsExtended(build_flags)
262
-
263
-		cxx = search_compiler()
264
-		cmd = ['"' + cxx + '"']
265
-
266
-		# Build flags from board.json
267
-		#if 'BOARD' in env:
268
-		#	cmd += [env.BoardConfig().get("build.extra_flags")]
269
-		for s in build_flags['CPPDEFINES']:
270
-			if isinstance(s, tuple):
271
-				cmd += ['-D' + s[0] + '=' + str(s[1])]
272
-			else:
273
-				cmd += ['-D' + s]
274
-
275
-		cmd += ['-D__MARLIN_DEPS__ -w -dM -E -x c++ buildroot/share/PlatformIO/scripts/common-dependencies.h']
276
-		cmd = ' '.join(cmd)
277
-		blab(cmd, 4)
278
-		define_list = subprocess.check_output(cmd, shell=True).splitlines()
203
+		from preprocessor import run_preprocessor
204
+		define_list = run_preprocessor(env)
279 205
 		marlin_features = {}
280 206
 		for define in define_list:
281 207
 			feature = define[8:].strip().decode().split(' ')
@@ -310,9 +236,18 @@ if pioutil.is_pio_build():
310 236
 	except:
311 237
 		pass
312 238
 
239
+	#
313 240
 	# Add a method for other PIO scripts to query enabled features
241
+	#
314 242
 	env.AddMethod(MarlinFeatureIsEnabled)
315 243
 
244
+	#
316 245
 	# Add dependencies for enabled Marlin features
246
+	#
317 247
 	apply_features_config()
318 248
 	force_ignore_unused_libs()
249
+
250
+	#print(env.Dump())
251
+
252
+	from signature import compute_build_signature
253
+	compute_build_signature(env)

+ 69
- 0
buildroot/share/PlatformIO/scripts/mc-apply.py View File

@@ -0,0 +1,69 @@
1
+#!/usr/bin/env python
2
+#
3
+# Create a Configuration from marlin_config.json
4
+#
5
+import json
6
+import sys
7
+import shutil
8
+import re
9
+
10
+opt_output = '--opt' in sys.argv
11
+output_suffix = '.sh' if opt_output else '' if '--bare-output' in sys.argv else '.gen'
12
+
13
+try:
14
+	with open('marlin_config.json', 'r') as infile:
15
+		conf = json.load(infile)
16
+		for key in conf:
17
+			# We don't care about the hash when restoring here
18
+			if key == '__INITIAL_HASH':
19
+				continue
20
+			if key == 'VERSION':
21
+				for k, v in sorted(conf[key].items()):
22
+					print(k + ': ' + v)
23
+				continue
24
+			# The key is the file name, so let's build it now
25
+			outfile = open('Marlin/' + key + output_suffix, 'w')
26
+			for k, v in sorted(conf[key].items()):
27
+				# Make define line now
28
+				if opt_output:
29
+					if v != '':
30
+						if '"' in v:
31
+							v = "'%s'" % v
32
+						elif ' ' in v:
33
+							v = '"%s"' % v
34
+						define = 'opt_set ' + k + ' ' + v + '\n'
35
+					else:
36
+						define = 'opt_enable ' + k + '\n'
37
+				else:
38
+					define = '#define ' + k + ' ' + v + '\n'
39
+				outfile.write(define)
40
+			outfile.close()
41
+
42
+			# Try to apply changes to the actual configuration file (in order to keep useful comments)
43
+			if output_suffix != '':
44
+				# Move the existing configuration so it doesn't interfere
45
+				shutil.move('Marlin/' + key, 'Marlin/' + key + '.orig')
46
+				infile_lines = open('Marlin/' + key + '.orig', 'r').read().split('\n')
47
+				outfile = open('Marlin/' + key, 'w')
48
+				for line in infile_lines:
49
+					sline = line.strip(" \t\n\r")
50
+					if sline[:7] == "#define":
51
+						# Extract the key here (we don't care about the value)
52
+						kv = sline[8:].strip().split(' ')
53
+						if kv[0] in conf[key]:
54
+							outfile.write('#define ' + kv[0] + ' ' + conf[key][kv[0]] + '\n')
55
+							# Remove the key from the dict, so we can still write all missing keys at the end of the file
56
+							del conf[key][kv[0]]
57
+						else:
58
+							outfile.write(line + '\n')
59
+					else:
60
+						outfile.write(line + '\n')
61
+				# Process any remaining defines here
62
+				for k, v in sorted(conf[key].items()):
63
+					define = '#define ' + k + ' ' + v + '\n'
64
+					outfile.write(define)
65
+				outfile.close()
66
+
67
+			print('Output configuration written to: ' + 'Marlin/' + key + output_suffix)
68
+except:
69
+	print('No marlin_config.json found.')

+ 99
- 0
buildroot/share/PlatformIO/scripts/preprocessor.py View File

@@ -0,0 +1,99 @@
1
+#
2
+# preprocessor.py
3
+#
4
+import subprocess,os,re
5
+
6
+verbose = 0
7
+
8
+def blab(str):
9
+	if verbose:
10
+		print(str)
11
+
12
+################################################################################
13
+#
14
+# Invoke GCC to run the preprocessor and extract enabled features
15
+#
16
+preprocessor_cache = {}
17
+def run_preprocessor(env, fn=None):
18
+	filename = fn or 'buildroot/share/PlatformIO/scripts/common-dependencies.h'
19
+	if filename in preprocessor_cache:
20
+		return preprocessor_cache[filename]
21
+
22
+	# Process defines
23
+	build_flags = env.get('BUILD_FLAGS')
24
+	build_flags = env.ParseFlagsExtended(build_flags)
25
+
26
+	cxx = search_compiler(env)
27
+	cmd = ['"' + cxx + '"']
28
+
29
+	# Build flags from board.json
30
+	#if 'BOARD' in env:
31
+	#	cmd += [env.BoardConfig().get("build.extra_flags")]
32
+	for s in build_flags['CPPDEFINES']:
33
+		if isinstance(s, tuple):
34
+			cmd += ['-D' + s[0] + '=' + str(s[1])]
35
+		else:
36
+			cmd += ['-D' + s]
37
+
38
+	cmd += ['-D__MARLIN_DEPS__ -w -dM -E -x c++']
39
+	depcmd = cmd + [ filename ]
40
+	cmd = ' '.join(depcmd)
41
+	blab(cmd)
42
+	define_list = subprocess.check_output(cmd, shell=True).splitlines()
43
+	preprocessor_cache[filename] = define_list
44
+	return define_list
45
+
46
+
47
+################################################################################
48
+#
49
+# Find a compiler, considering the OS
50
+#
51
+def search_compiler(env):
52
+
53
+	ENV_BUILD_PATH = os.path.join(env.Dictionary('PROJECT_BUILD_DIR'), env['PIOENV'])
54
+	GCC_PATH_CACHE = os.path.join(ENV_BUILD_PATH, ".gcc_path")
55
+
56
+	try:
57
+		filepath = env.GetProjectOption('custom_gcc')
58
+		blab("Getting compiler from env")
59
+		return filepath
60
+	except:
61
+		pass
62
+
63
+	if os.path.exists(GCC_PATH_CACHE):
64
+		blab("Getting g++ path from cache")
65
+		with open(GCC_PATH_CACHE, 'r') as f:
66
+			return f.read()
67
+
68
+	# Find the current platform compiler by searching the $PATH
69
+	# which will be in a platformio toolchain bin folder
70
+	path_regex = re.escape(env['PROJECT_PACKAGES_DIR'])
71
+	gcc = "g++"
72
+	if env['PLATFORM'] == 'win32':
73
+		path_separator = ';'
74
+		path_regex += r'.*\\bin'
75
+		gcc += ".exe"
76
+	else:
77
+		path_separator = ':'
78
+		path_regex += r'/.+/bin'
79
+
80
+	# Search for the compiler
81
+	for pathdir in env['ENV']['PATH'].split(path_separator):
82
+		if not re.search(path_regex, pathdir, re.IGNORECASE):
83
+			continue
84
+		for filepath in os.listdir(pathdir):
85
+			if not filepath.endswith(gcc):
86
+				continue
87
+			# Use entire path to not rely on env PATH
88
+			filepath = os.path.sep.join([pathdir, filepath])
89
+			# Cache the g++ path to no search always
90
+			if os.path.exists(ENV_BUILD_PATH):
91
+				blab("Caching g++ for current env")
92
+				with open(GCC_PATH_CACHE, 'w+') as f:
93
+					f.write(filepath)
94
+
95
+			return filepath
96
+
97
+	filepath = env.get('CXX')
98
+	blab("Couldn't find a compiler! Fallback to %s" % filepath)
99
+	return filepath

+ 176
- 0
buildroot/share/PlatformIO/scripts/signature.py View File

@@ -0,0 +1,176 @@
1
+#
2
+# signature.py
3
+#
4
+import os,subprocess,re,json,hashlib
5
+
6
+#
7
+# The dumbest preprocessor in the world
8
+# Extract macro name from an header file and store them in an array
9
+# No processing is done here, so they are raw values here and it does not match what actually enabled
10
+# in the file (since you can have #if SOMETHING_UNDEFINED / #define BOB / #endif)
11
+# But it's useful to filter the useful macro spit out by the preprocessor from noise from the system
12
+# headers.
13
+#
14
+def extract_defines(filepath):
15
+	f = open(filepath).read().split("\n")
16
+	a = []
17
+	for line in f:
18
+		sline = line.strip(" \t\n\r")
19
+		if sline[:7] == "#define":
20
+			# Extract the key here (we don't care about the value)
21
+			kv = sline[8:].strip().split(' ')
22
+			a.append(kv[0])
23
+	return a
24
+
25
+# Compute the SHA256 hash of a file
26
+def get_file_sha256sum(filepath):
27
+	sha256_hash = hashlib.sha256()
28
+	with open(filepath,"rb") as f:
29
+		# Read and update hash string value in blocks of 4K
30
+		for byte_block in iter(lambda: f.read(4096),b""):
31
+			sha256_hash.update(byte_block)
32
+	return sha256_hash.hexdigest()
33
+
34
+#
35
+# Compress a JSON file into a zip file
36
+#
37
+import zipfile
38
+def compress_file(filepath, outputbase):
39
+	with zipfile.ZipFile(outputbase + '.zip', 'w', compression=zipfile.ZIP_BZIP2, compresslevel=9) as zipf:
40
+		zipf.write(filepath, compress_type=zipfile.ZIP_BZIP2, compresslevel=9)
41
+
42
+#
43
+# Compute the build signature. The idea is to extract all defines in the configuration headers
44
+# to build a unique reversible signature from this build so it can be included in the binary
45
+# We can reverse the signature to get a 1:1 equivalent configuration file
46
+#
47
+def compute_build_signature(env):
48
+	if 'BUILD_SIGNATURE' in env:
49
+		return
50
+
51
+	# Definitions from these files will be kept
52
+	files_to_keep = [ 'Marlin/Configuration.h', 'Marlin/Configuration_adv.h' ]
53
+
54
+	build_dir=os.path.join(env['PROJECT_BUILD_DIR'], env['PIOENV'])
55
+
56
+	# Check if we can skip processing
57
+	hashes = ''
58
+	for header in files_to_keep:
59
+		hashes += get_file_sha256sum(header)[0:10]
60
+
61
+	marlin_json = os.path.join(build_dir, 'marlin_config.json')
62
+	marlin_zip = os.path.join(build_dir, 'mc')
63
+
64
+	# Read existing config file
65
+	try:
66
+		with open(marlin_json, 'r') as infile:
67
+			conf = json.load(infile)
68
+			if conf['__INITIAL_HASH'] == hashes:
69
+				# Same configuration, skip recomputing the building signature
70
+				compress_file(marlin_json, marlin_zip)
71
+				return
72
+	except:
73
+		pass
74
+
75
+	# Get enabled config options based on preprocessor
76
+	from preprocessor import run_preprocessor
77
+	complete_cfg = run_preprocessor(env)
78
+
79
+	# Dumb #define extraction from the configuration files
80
+	real_defines = {}
81
+	all_defines = []
82
+	for header in files_to_keep:
83
+		defines = extract_defines(header)
84
+		# To filter only the define we want
85
+		all_defines = all_defines + defines
86
+		# To remember from which file it cames from
87
+		real_defines[header.split('/')[-1]] = defines
88
+
89
+	r = re.compile(r"\(+(\s*-*\s*_.*)\)+")
90
+
91
+	# First step is to collect all valid macros
92
+	defines = {}
93
+	for line in complete_cfg:
94
+
95
+		# Split the define from the value
96
+		key_val = line[8:].strip().decode().split(' ')
97
+		key, value = key_val[0], ' '.join(key_val[1:])
98
+
99
+		# Ignore values starting with two underscore, since it's low level
100
+		if len(key) > 2 and key[0:2] == "__" :
101
+			continue
102
+		# Ignore values containing a parenthesis (likely a function macro)
103
+		if '(' in key and ')' in key:
104
+			continue
105
+
106
+		# Then filter dumb values
107
+		if r.match(value):
108
+			continue
109
+
110
+		defines[key] = value if len(value) else ""
111
+
112
+	if not 'CONFIGURATION_EMBEDDING' in defines:
113
+		return
114
+
115
+	# Second step is to filter useless macro
116
+	resolved_defines = {}
117
+	for key in defines:
118
+		# Remove all boards now
119
+		if key[0:6] == "BOARD_" and key != "BOARD_INFO_NAME":
120
+			continue
121
+		# Remove all keys ending by "_NAME" as it does not make a difference to the configuration
122
+		if key[-5:] == "_NAME" and key != "CUSTOM_MACHINE_NAME":
123
+			continue
124
+		# Remove all keys ending by "_T_DECLARED" as it's a copy of not important system stuff
125
+		if key[-11:] == "_T_DECLARED":
126
+			continue
127
+		# Remove keys that are not in the #define list in the Configuration list
128
+		if not (key in all_defines) and key != "DETAILED_BUILD_VERSION" and key != "STRING_DISTRIBUTION_DATE":
129
+			continue
130
+
131
+		# Don't be that smart guy here
132
+		resolved_defines[key] = defines[key]
133
+
134
+	# Generate a build signature now
135
+	# We are making an object that's a bit more complex than a basic dictionary here
136
+	data = {}
137
+	data['__INITIAL_HASH'] = hashes
138
+	# First create a key for each header here
139
+	for header in real_defines:
140
+		data[header] = {}
141
+
142
+	# Then populate the object where each key is going to (that's a O(N^2) algorithm here...)
143
+	for key in resolved_defines:
144
+		for header in real_defines:
145
+			if key in real_defines[header]:
146
+				data[header][key] = resolved_defines[key]
147
+
148
+	# Append the source code version and date
149
+	data['VERSION'] = {}
150
+	data['VERSION']['DETAILED_BUILD_VERSION'] = resolved_defines['DETAILED_BUILD_VERSION']
151
+	data['VERSION']['STRING_DISTRIBUTION_DATE'] = resolved_defines['STRING_DISTRIBUTION_DATE']
152
+	try:
153
+		curver = subprocess.check_output(["git", "describe", "--match=NeVeRmAtCh", "--always"]).strip()
154
+		data['VERSION']['GIT_REF'] = curver.decode()
155
+	except:
156
+		pass
157
+
158
+	with open(marlin_json, 'w') as outfile:
159
+		json.dump(data, outfile, separators=(',', ':'))
160
+
161
+	# Compress the JSON file as much as we can
162
+	compress_file(marlin_json, marlin_zip)
163
+
164
+	# Generate a C source file for storing this array
165
+	with open('Marlin/src/mczip.h','wb') as result_file:
166
+		result_file.write(b'#warning "Generated file \'mc.zip\' is embedded"\n')
167
+		result_file.write(b'const unsigned char mc_zip[] PROGMEM = {\n ')
168
+		count = 0
169
+		for b in open(os.path.join(build_dir, 'mc.zip'), 'rb').read():
170
+			result_file.write(b' 0x%02X,' % b)
171
+			count += 1
172
+			if (count % 16 == 0):
173
+			 	result_file.write(b'\n ')
174
+		if (count % 16):
175
+			result_file.write(b'\n')
176
+		result_file.write(b'};\n')

+ 19
- 0
docs/ConfigEmbedding.md View File

@@ -0,0 +1,19 @@
1
+# Configuration Embedding
2
+
3
+Starting with version 2.0.9.3, Marlin automatically extracts the configuration used to generate the firmware and stores it in the firmware binary. This is enabled by defining `CONFIGURATION_EMBEDDING` in `Configuration_adv.h`.
4
+
5
+## How it's done
6
+To create the embedded configuration, we do a compiler pass to process the Configuration files and extract all active options. The active options are parsed into key/value pairs, serialized to JSON format, and stored in a file called `marlin_config.json`, which also includes specific build information (like the git revision, the build date, and some version information. The JSON file is then compressed in a ZIP archive called `.pio/build/mc.zip` which is converted into a C array and stored in a C++ file called `mc.h` which is included in the build.
7
+
8
+## Extracting configurations from a Marlin binary
9
+To get the configuration out of a binary firmware, you'll need a non-write-protected SD card inserted into the printer while running the firmware.
10
+Send the command `M503 C` to write the file `mc.zip` to the SD card. Copy the file to your computer, ideally in the same folder as the Marlin repository.
11
+
12
+Run the following commands to extract and apply the configuration:
13
+```
14
+$ git checkout -f
15
+$ unzip mc.zip 
16
+$ python buildroot/share/PlatformIO/scripts/mc-apply.py
17
+```
18
+
19
+This will attempt to update the configuration files to match the settings used for the original build. It will also dump the git reference used to build the code (which may be accessible if the firmware was built from the main repository. As a fallback it also includes the `STRING_DISTRIBUTION_DATE` which is unlikely to be modified in a fork).

Loading…
Cancel
Save