Gemini Xrossband (GemX) - LR1121 Driver (#2540)
[ExpressLRS.git] / src / python / UnifiedConfiguration.py
blob3718644bd653334c60d370b525acd4274ad4d4d6
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 layout = (json.JSONEncoder().encode(hardware).encode() + (b'\0' * 2048))[0:2048]
51 firmware_file.write(layout)
52 except EnvironmentError:
53 sys.stderr.write(f'Error opening file "{layout_file}"\n')
54 exit(1)
55 else:
56 firmware_file.write(b'\0' * 2048)
57 if config is not None and 'logo_file' in config:
58 logo_file = f"hardware/logo/{config['logo_file']}"
59 with open(logo_file, 'rb') as f:
60 firmware_file.write(f.read())
61 if config is not None and 'prior_target_name' in config:
62 firmware_file.write(b'\xBE\xEF\xCA\xFE')
63 firmware_file.write(config['prior_target_name'].upper().encode())
64 firmware_file.write(b'\0')
66 def doConfiguration(file, defines, config, moduletype, frequency, platform, device_name):
67 product_name = "Unified"
68 lua_name = "Unified"
69 layout = None
71 targets = {}
72 with open('hardware/targets.json') as f:
73 targets = json.load(f)
75 if config is not None:
76 config ='.'.join(map(lambda s: f'"{s}"', config.split('.')))
77 config = jmespath.search(config, targets)
78 elif not sys.stdin.isatty():
79 print('Not running in an interactive shell, leaving the firmware "bare".\n')
80 print('The current compile options (user defines) have been included.')
81 print('You will be able to configure the hardware via the web UI on the device.')
82 else:
83 products = []
84 i = 0
85 for k in jmespath.search(f'[*."{moduletype}_{frequency}".*][][?platform==`{platform}`][].product_name', targets):
86 i += 1
87 products.append(k)
88 print(f"{i}) {k}")
89 print('Choose a configuration to load into the firmware file (press enter to leave bare)')
90 choice = input()
91 if choice != "":
92 config = products[int(choice)-1]
93 config = jmespath.search(f'[*."{moduletype}_{frequency}".*][][?product_name==`{config}`][]', targets)[0]
95 if config is not None:
96 product_name = config['product_name']
97 lua_name = config['lua_name']
98 dir = 'TX' if moduletype == 'tx' else 'RX'
99 layout = f"hardware/{dir}/{config['layout_file']}"
101 lua_name = lua_name if device_name is None else device_name
102 appendToFirmware(file, product_name, lua_name, defines, config, layout)
104 def appendConfiguration(source, target, env):
105 target_name = env.get('PIOENV', '').upper()
106 device_name = env.get('DEVICE_NAME', None)
107 config = env.GetProjectOption('board_config', None)
108 if 'UNIFIED_' not in target_name and config is None:
109 return
111 moduletype = ''
112 frequency = ''
113 if config is not None:
114 moduletype = 'tx' if '.tx_' in config else 'rx'
115 frequency = '2400' if '_2400.' in config else '900' if '_900.' in config else 'dual'
116 else:
117 moduletype = 'tx' if '_TX_' in target_name else 'rx'
118 frequency = '2400' if '_2400_' in target_name else '900' if '_900_' in target_name else 'dual'
120 if env.get('PIOPLATFORM', '') == 'espressif32':
121 platform = 'esp32'
122 if 'esp32-s3' in env.get('BOARD', ''):
123 platform = 'esp32-s3'
124 else:
125 platform = 'esp8285'
127 defines = json.JSONEncoder().encode(env['OPTIONS_JSON'])
129 with open(str(target[0]), "r+b") as firmware_file:
130 doConfiguration(firmware_file, defines, config, moduletype, frequency, platform, device_name)
132 if __name__ == '__main__':
133 parser = argparse.ArgumentParser(description="Configure Unified Firmware")
134 parser.add_argument("--target", type=str, help="Path into 'targets.json' file for hardware configuration")
135 parser.add_argument("--options", type=str, help="JSON document with configuration options")
137 parser.add_argument("file", type=argparse.FileType("r+b"), help="The firmware file to configure")
139 args = parser.parse_args()
141 targets = {}
142 with open('hardware/targets.json') as f:
143 targets = json.load(f)
145 moduletype = 'tx' if '.tx_' in args.target else 'rx'
147 config ='.'.join(map(lambda s: f'"{s}"', args.target.split('.')))
148 config = jmespath.search(config, targets)
150 if config is not None:
151 product_name = config['product_name']
152 lua_name = config['lua_name']
153 dir = 'TX' if moduletype == 'tx' else 'RX'
154 layout = f"hardware/{dir}/{config['layout_file']}"
156 appendToFirmware(args.file, product_name, lua_name, args.options, config, layout)