Append prior target name to firmware (#2306)
[ExpressLRS.git] / src / python / UnifiedConfiguration.py
blobdabaf072d9ddec914724b7c5a625f98532dd76a0
1 #!/usr/bin/python
3 import argparse
4 import json
5 import struct
6 import sys
8 from external import jmespath
10 def findFirmwareEnd(f):
11 f.seek(0, 0)
12 (magic, segments, _, _, _) = struct.unpack('<BBBBI', f.read(8))
13 if magic != 0xe9:
14 sys.stderr.write('The file provided does not the right magic for a firmware file!\n')
15 exit(1)
17 is8285 = False
18 if segments == 2: # we have to assume it's an ESP8266/85
19 f.seek(0x1000, 0)
20 (magic, segments, _, _, _) = struct.unpack('<BBBBI', f.read(8))
21 is8285 = True
22 else:
23 f.seek(24, 0)
25 for _ in range(segments):
26 (_, size) = struct.unpack('<II', f.read(8))
27 f.seek(size, 1)
29 pos = f.tell()
30 pos = (pos + 16) & ~15
31 if not is8285:
32 pos = pos + 32
33 return pos
35 def appendToFirmware(firmware_file, product_name, lua_name, defines, config, layout_file):
36 product = (product_name.encode() + (b'\0' * 128))[0:128]
37 device = (lua_name.encode() + (b'\0' * 16))[0:16]
38 end = findFirmwareEnd(firmware_file)
39 firmware_file.seek(end, 0)
40 firmware_file.write(product)
41 firmware_file.write(device)
42 defines = (defines.encode() + (b'\0' * 512))[0:512]
43 firmware_file.write(defines)
44 if layout_file is not None:
45 try:
46 with open(layout_file) as h:
47 hardware = json.load(h)
48 if 'overlay' in config:
49 hardware.update(config['overlay'])
50 firmware_file.write(json.JSONEncoder().encode(hardware).encode())
51 except EnvironmentError:
52 sys.stderr.write(f'Error opening file "{layout_file}"\n')
53 exit(1)
54 firmware_file.write(b'\0')
55 if config is not None and 'prior_target_name' in config:
56 firmware_file.write(b'\xBE\xEF\xCA\xFE')
57 firmware_file.write(config['prior_target_name'].upper().encode())
58 firmware_file.write(b'\0')
60 def doConfiguration(file, defines, config, moduletype, frequency, platform, device_name):
61 product_name = "Unified"
62 lua_name = "Unified"
63 layout = None
65 targets = {}
66 with open('hardware/targets.json') as f:
67 targets = json.load(f)
69 if config is not None:
70 config ='.'.join(map(lambda s: f'"{s}"', config.split('.')))
71 config = jmespath.search(config, targets)
72 elif not sys.stdin.isatty():
73 print('Not running in an interactive shell, leaving the firmware "bare".\n')
74 print('The current compile options (user defines) have been included.')
75 print('You will be able to configure the hardware via the web UI on the device.')
76 else:
77 products = []
78 i = 0
79 for k in jmespath.search(f'[*."{moduletype}_{frequency}".*][][?platform==`{platform}`][].product_name', targets):
80 i += 1
81 products.append(k)
82 print(f"{i}) {k}")
83 print('Choose a configuration to load into the firmware file (press enter to leave bare)')
84 choice = input()
85 if choice != "":
86 config = products[int(choice)-1]
87 config = jmespath.search(f'[*."{moduletype}_{frequency}".*][][?product_name==`{config}`][]', targets)[0]
89 if config is not None:
90 product_name = config['product_name']
91 lua_name = config['lua_name']
92 dir = 'TX' if moduletype == 'tx' else 'RX'
93 layout = f"hardware/{dir}/{config['layout_file']}"
95 lua_name = lua_name if device_name is None else device_name
96 appendToFirmware(file, product_name, lua_name, defines, config, layout)
98 def appendConfiguration(source, target, env):
99 target_name = env.get('PIOENV', '').upper()
100 device_name = env.get('DEVICE_NAME', None)
101 config = env.GetProjectOption('board_config', None)
102 if 'UNIFIED_' not in target_name and config is None:
103 return
105 moduletype = ''
106 frequency = ''
107 if config is not None:
108 moduletype = 'tx' if '.tx_' in config else 'rx'
109 frequency = '2400' if '_2400.' in config else '900'
110 else:
111 moduletype = 'tx' if '_TX_' in target_name else 'rx'
112 frequency = '2400' if '_2400_' in target_name else '900'
114 platform = 'esp32' if env.get('PIOPLATFORM', '') in ['espressif32'] else 'esp8285'
116 defines = json.JSONEncoder().encode(env['OPTIONS_JSON'])
118 with open(str(target[0]), "r+b") as firmware_file:
119 doConfiguration(firmware_file, defines, config, moduletype, frequency, platform, device_name)
121 if __name__ == '__main__':
122 parser = argparse.ArgumentParser(description="Configure Unified Firmware")
123 parser.add_argument("--target", type=str, help="Path into 'targets.json' file for hardware configuration")
124 parser.add_argument("--options", type=str, help="JSON document with configuration options")
126 parser.add_argument("file", type=argparse.FileType("r+b"), help="The firmware file to configure")
128 args = parser.parse_args()
130 targets = {}
131 with open('hardware/targets.json') as f:
132 targets = json.load(f)
134 moduletype = 'tx' if '.tx_' in args.target else 'rx'
136 config ='.'.join(map(lambda s: f'"{s}"', args.target.split('.')))
137 config = jmespath.search(config, targets)
139 if config is not None:
140 product_name = config['product_name']
141 lua_name = config['lua_name']
142 dir = 'TX' if moduletype == 'tx' else 'RX'
143 layout = f"hardware/{dir}/{config['layout_file']}"
145 appendToFirmware(args.file, product_name, lua_name, args.options, config, layout)