CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/scripts/apj_tool.py
Views: 1798
1
#!/usr/bin/env python3
2
'''
3
tool to manipulate ArduPilot firmware files, changing default parameters
4
'''
5
6
import os, sys, struct, json, base64, zlib, hashlib
7
8
import argparse
9
10
def to_ascii(s):
11
'''get ascii string'''
12
if sys.version_info.major >= 3:
13
return str(s, 'ascii')
14
else:
15
return str(s)
16
17
def to_bytes(s):
18
'''get bytes string'''
19
if sys.version_info.major >= 3:
20
if isinstance(s,bytes):
21
return s
22
if isinstance(s,int):
23
s = chr(s)
24
return bytes(s, 'ascii')
25
else:
26
return bytes(s)
27
28
class embedded_defaults(object):
29
'''class to manipulate embedded defaults in a firmware'''
30
def __init__(self, filename):
31
self.filename = filename
32
self.offset = 0
33
self.max_len = 0
34
self.extension = os.path.splitext(filename)[1]
35
if self.extension.lower() in ['.apj', '.px4']:
36
self.load_apj()
37
elif self.extension.lower() in ['.abin']:
38
self.load_abin()
39
else:
40
self.load_binary()
41
42
def load_binary(self):
43
'''load firmware from binary file'''
44
f = open(self.filename,'rb')
45
self.firmware = f.read()
46
f.close()
47
print("Loaded binary file of length %u" % len(self.firmware))
48
49
def load_abin(self):
50
'''load firmware from abin file'''
51
f = open(self.filename,'r')
52
self.headers = []
53
while True:
54
line = f.readline().rstrip()
55
if line == '--':
56
break
57
self.headers.append(line)
58
if len(self.headers) > 50:
59
print("Error: too many abin headers")
60
sys.exit(1)
61
self.firmware = f.read()
62
f.close()
63
print("Loaded abin file of length %u" % len(self.firmware))
64
65
def load_apj(self):
66
'''load firmware from a json apj or px4 file'''
67
f = open(self.filename,'r')
68
self.fw_json = json.load(f)
69
f.close()
70
self.firmware = zlib.decompress(base64.b64decode(self.fw_json['image']))
71
print("Loaded apj file of length %u" % len(self.firmware))
72
73
def save_binary(self):
74
'''save binary file'''
75
f = open(self.filename, 'wb')
76
f.write(self.firmware)
77
f.close()
78
print("Saved binary of length %u" % len(self.firmware))
79
80
def save_apj(self):
81
'''save apj file'''
82
self.fw_json['image'] = to_ascii(base64.b64encode(zlib.compress(self.firmware, 9)))
83
f = open(self.filename,'w')
84
json.dump(self.fw_json,f,indent=4)
85
f.truncate()
86
f.close()
87
print("Saved apj of length %u" % len(self.firmware))
88
89
def save_abin(self):
90
'''save abin file'''
91
f = open(self.filename,'w')
92
for i in range(len(self.headers)):
93
line = self.headers[i]
94
if line.startswith('MD5: '):
95
h = hashlib.new('md5')
96
h.update(self.firmware)
97
f.write('MD5: %s\n' % h.hexdigest())
98
else:
99
f.write(line+'\n')
100
f.write('--\n')
101
f.write(self.firmware)
102
f.close()
103
print("Saved abin of length %u" % len(self.firmware))
104
105
def find(self):
106
'''find defaults in firmware'''
107
# these are the magic headers from AP_Param.cpp
108
magic_str = "PARMDEF".encode('ascii')
109
param_magic = [ 0x55, 0x37, 0xf4, 0xa0, 0x38, 0x5d, 0x48, 0x5b ]
110
def u_ord(c):
111
return ord(c) if sys.version_info.major < 3 else c
112
113
while True:
114
i = self.firmware[self.offset:].find(magic_str)
115
if i == -1:
116
print("No param area found")
117
return None
118
matched = True
119
for j in range(len(param_magic)):
120
if u_ord(self.firmware[self.offset+i+j+8]) != param_magic[j]:
121
matched = False
122
break
123
if not matched:
124
self.offset += i+8
125
continue
126
self.offset += i
127
self.max_len, self.length = struct.unpack("<HH", self.firmware[self.offset+16:self.offset+20])
128
return True
129
130
def contents(self):
131
'''return current contents'''
132
contents = self.firmware[self.offset+20:self.offset+20+self.length]
133
# remove carriage returns
134
contents = contents.replace(b'\r',b'')
135
return contents
136
137
def set_contents(self, contents):
138
'''set new defaults as a string'''
139
length = len(contents)
140
if length > self.max_len:
141
print("Error: Length %u larger than maximum %u" % (length, self.max_len))
142
sys.exit(1)
143
new_fw = self.firmware[:self.offset+18]
144
new_fw += struct.pack("<H", length)
145
new_fw += to_bytes(contents)
146
new_fw += self.firmware[self.offset+20+length:]
147
self.firmware = new_fw
148
self.length = len(contents)
149
150
def set_file(self, filename):
151
'''set defaults to contents of a file'''
152
print("Setting defaults from %s" % filename)
153
f = open(filename, 'r')
154
contents = f.read()
155
f.close()
156
# remove carriage returns from the file
157
contents = contents.replace('\r','')
158
self.set_contents(contents)
159
160
def split_multi(self, s, separators):
161
'''split a string, handling multiple separators'''
162
for sep in separators:
163
s = s.replace(to_bytes(sep), b' ')
164
return s.split()
165
166
def set_one(self, set):
167
'''set a single parameter'''
168
v = set.split('=')
169
if len(v) != 2:
170
print("Error: set takes form NAME=VALUE")
171
sys.exit(1)
172
param_name = to_bytes(v[0].upper())
173
param_value = to_bytes(v[1])
174
175
contents = self.contents()
176
lines = contents.strip().split(b'\n')
177
changed = False
178
for i in range(len(lines)):
179
a = self.split_multi(lines[i], b", =\t")
180
if len(a) != 2:
181
continue
182
if a[0].upper() == param_name:
183
separator = to_bytes(lines[i][len(param_name)])
184
lines[i] = b'%s%s%s' % (param_name, separator, param_value)
185
changed = True
186
if not changed:
187
lines.append(to_bytes('%s=%s' % (to_ascii(param_name), to_ascii(param_value))))
188
contents = b'\n'.join(lines)
189
contents = contents.lstrip() + b'\n'
190
self.set_contents(contents)
191
192
def save(self):
193
'''save new firmware'''
194
if self.extension.lower() in ['.apj', '.px4']:
195
self.save_apj()
196
elif self.extension.lower() in ['.abin']:
197
self.save_abin()
198
else:
199
self.save_binary()
200
201
def extract(self):
202
'''extract firmware image to *.bin'''
203
a = os.path.splitext(self.filename)
204
if len(a) == 1:
205
a.append('.bin')
206
else:
207
a = (a[0], '.bin')
208
binfile = ''.join(a)
209
print("Extracting firmware to %s" % binfile)
210
f = open(binfile,'wb')
211
f.write(self.firmware)
212
f.close()
213
214
215
def defaults_contents(firmware, ofs, length):
216
'''return current defaults contents'''
217
return firmware
218
219
if __name__ == '__main__':
220
parser = argparse.ArgumentParser(description='manipulate parameter defaults in an ArduPilot firmware')
221
222
parser.add_argument('firmware_file')
223
parser.add_argument('--set-file', type=str, default=None, help='replace parameter defaults from a file')
224
parser.add_argument('--set', type=str, default=None, help='replace one parameter default, in form NAME=VALUE')
225
parser.add_argument('--show', action='store_true', default=False, help='show current parameter defaults')
226
parser.add_argument('--extract', action='store_true', default=False, help='extract firmware image to *.bin')
227
228
args = parser.parse_args()
229
230
defaults = embedded_defaults(args.firmware_file)
231
232
have_defaults = defaults.find()
233
234
if not have_defaults and not args.extract:
235
print("Error: Param defaults support not found in firmware; see https://ardupilot.org/copter/docs/common-oem-customizations.html for embedding defaults.parm")
236
sys.exit(1)
237
238
if have_defaults:
239
print("Found param defaults max_length=%u length=%u" % (defaults.max_len, defaults.length))
240
241
if args.set_file:
242
# load new defaults from a file
243
defaults.set_file(args.set_file)
244
defaults.save()
245
246
if args.set:
247
# set a single parameter
248
defaults.set_one(args.set)
249
defaults.save()
250
251
if args.show:
252
# show all defaults
253
print(to_ascii(defaults.contents()))
254
255
if args.extract:
256
defaults.extract()
257
258