Add tlmConfirm to tlm_dl ota packet-structure (#2991)
[ExpressLRS.git] / src / python / UnifiedConfiguration.py
blob1cae49aee836ecf0abcf524151c7f143c508d479
1 #!/usr/bin/python
3 import argparse
4 import json
5 import struct
6 import sys
8 from external import jmespath
9 from firmware import TXType
12 def findFirmwareEnd(f):
13 f.seek(0, 0)
14 (magic, segments, _, _, _) = struct.unpack('<BBBBI', f.read(8))
15 if magic != 0xe9:
16 sys.stderr.write('The file provided does not the right magic for a firmware file!\n')
17 exit(1)
19 is8285 = False
20 if segments == 2: # we have to assume it's an ESP8266/85
21 f.seek(0x1000, 0)
22 (magic, segments, _, _, _) = struct.unpack('<BBBBI', f.read(8))
23 is8285 = True
24 else:
25 f.seek(24, 0)
27 for _ in range(segments):
28 (_, size) = struct.unpack('<II', f.read(8))
29 f.seek(size, 1)
31 pos = f.tell()
32 pos = (pos + 16) & ~15
33 if not is8285:
34 pos = pos + 32
35 return pos
37 def appendToFirmware(firmware_file, product_name, lua_name, defines, config, layout_file, rx_as_tx):
38 product = (product_name.encode() + (b'\0' * 128))[0:128]
39 device = (lua_name.encode() + (b'\0' * 16))[0:16]
40 end = findFirmwareEnd(firmware_file)
41 firmware_file.seek(end, 0)
42 firmware_file.write(product)
43 firmware_file.write(device)
44 defines = (defines.encode() + (b'\0' * 512))[0:512]
45 firmware_file.write(defines)
46 if layout_file is not None:
47 try:
48 with open(layout_file) as h:
49 hardware = json.load(h)
50 if 'overlay' in config:
51 hardware.update(config['overlay'])
52 if rx_as_tx is not None:
53 if 'serial_rx' not in hardware or 'serial_tx' not in hardware:
54 sys.stderr.write(f'Cannot select this target as RX-as-TX\n')
55 exit(1)
56 if rx_as_tx == TXType.external and hardware['serial_rx']:
57 hardware['serial_rx'] = hardware['serial_tx']
58 if 'led_red' not in hardware and 'led' in hardware:
59 hardware['led_red'] = hardware['led']
60 del hardware['led']
61 layout = (json.JSONEncoder().encode(hardware).encode() + (b'\0' * 2048))[0:2048]
62 firmware_file.write(layout)
63 except EnvironmentError:
64 sys.stderr.write(f'Error opening file "{layout_file}"\n')
65 exit(1)
66 else:
67 firmware_file.write(b'\0' * 2048)
68 if config is not None and 'logo_file' in config:
69 logo_file = f"hardware/logo/{config['logo_file']}"
70 with open(logo_file, 'rb') as f:
71 firmware_file.write(f.read())
72 if config is not None and 'prior_target_name' in config:
73 firmware_file.write(b'\xBE\xEF\xCA\xFE')
74 firmware_file.write(config['prior_target_name'].upper().encode())
75 firmware_file.write(b'\0')
77 def doConfiguration(file, defines, config, moduletype, frequency, platform, device_name, rx_as_tx):
78 product_name = "Unified"
79 lua_name = "Unified"
80 layout = None
82 targets = {}
83 with open('hardware/targets.json') as f:
84 targets = json.load(f)
86 if config is not None:
87 config ='.'.join(map(lambda s: f'"{s}"', config.split('.')))
88 config = jmespath.search(config, targets)
89 elif not sys.stdin.isatty():
90 print('Not running in an interactive shell, leaving the firmware "bare".\n')
91 print('The current compile options (user defines) have been included.')
92 print('You will be able to configure the hardware via the web UI on the device.')
93 else:
94 products = []
95 i = 0
96 for k in jmespath.search(f'[*."{moduletype}_{frequency}".*][][?platform==`{platform}`][].product_name', targets):
97 i += 1
98 products.append(k)
99 print(f"{i}) {k}")
100 print('Choose a configuration to load into the firmware file (press enter to leave bare)')
101 choice = input()
102 if choice != "":
103 config = products[int(choice)-1]
104 config = jmespath.search(f'[*."{moduletype}_{frequency}".*][][?product_name==`{config}`][]', targets)[0]
106 if config is not None:
107 product_name = config['product_name']
108 lua_name = config['lua_name']
109 dir = 'TX' if moduletype == 'tx' else 'RX'
110 layout = f"hardware/{dir}/{config['layout_file']}"
112 lua_name = lua_name if device_name is None else device_name
113 appendToFirmware(file, product_name, lua_name, defines, config, layout, rx_as_tx)
115 def appendConfiguration(source, target, env):
116 target_name = env.get('PIOENV', '').upper()
117 device_name = env.get('DEVICE_NAME', None)
118 config = env.GetProjectOption('board_config', None)
119 if 'UNIFIED_' not in target_name and config is None:
120 return
122 moduletype = ''
123 frequency = ''
124 if config is not None:
125 moduletype = 'tx' if '.tx_' in config else 'rx'
126 frequency = '2400' if '_2400.' in config else '900' if '_900.' in config else 'dual'
127 else:
128 moduletype = 'tx' if '_TX_' in target_name else 'rx'
129 frequency = '2400' if '_2400_' in target_name else '900' if '_900_' in target_name else 'dual'
131 if env.get('PIOPLATFORM', '') == 'espressif32':
132 platform = 'esp32'
133 if 'esp32-s3' in env.get('BOARD', ''):
134 platform = 'esp32-s3'
135 elif 'esp32-c3' in env.get('BOARD', ''):
136 platform = 'esp32-c3'
137 else:
138 platform = 'esp8285'
140 defines = json.JSONEncoder().encode(env['OPTIONS_JSON'])
142 with open(str(target[0]), "r+b") as firmware_file:
143 doConfiguration(firmware_file, defines, config, moduletype, frequency, platform, device_name, None)
145 if __name__ == '__main__':
146 parser = argparse.ArgumentParser(description="Configure Unified Firmware")
147 parser.add_argument("--target", type=str, help="Path into 'targets.json' file for hardware configuration")
148 parser.add_argument("--options", type=str, help="JSON document with configuration options")
150 parser.add_argument("file", type=argparse.FileType("r+b"), help="The firmware file to configure")
152 args = parser.parse_args()
154 targets = {}
155 with open('hardware/targets.json') as f:
156 targets = json.load(f)
158 moduletype = 'tx' if '.tx_' in args.target else 'rx'
160 config ='.'.join(map(lambda s: f'"{s}"', args.target.split('.')))
161 config = jmespath.search(config, targets)
163 if config is not None:
164 product_name = config['product_name']
165 lua_name = config['lua_name']
166 dir = 'TX' if moduletype == 'tx' else 'RX'
167 layout = f"hardware/{dir}/{config['layout_file']}"
169 appendToFirmware(args.file, product_name, lua_name, args.options, config, layout, None)