-
Notifications
You must be signed in to change notification settings - Fork 0
/
checkArtooAndUpdate.py
292 lines (253 loc) · 10.5 KB
/
checkArtooAndUpdate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!/usr/bin/env python
import glob
import serial
import slip
import os
import subprocess
import sys
import time
import logging
import logging.config
import param_stored_vals_msg # Used for reading stick calibration values
import slip, struct # Used for writing stick calibration values
sololink_conf = "/etc/sololink.conf"
ARTOO_SYSINFO_ID = chr(0x3)
ARTOO_UPDATE_ID = chr(0x12)
ARTOO_LOCKOUT_ID = chr(0x13)
ARTOO_CALIBRATE_ID = chr(0x2)
ARTOO_UPDATE_SUCCESS = chr(1)
ARTOO_UPDATE_FAILED = chr(2)
ARTOO_LOCKOUT_FALSE = chr(0)
ARTOO_LOCKOUT_TRUE = chr(1)
ARTOO_BAUD = 115200
# update_result should be either ARTOO_UPDATE_SUCCESS or ARTOO_UPDATE_FAILED
def setArtooUpdateComplete(update_result):
ser = serial.Serial("/dev/ttymxc1", 115200, timeout=1)
slipdev = slip.SlipDevice(ser)
slipdev.write("".join([ARTOO_UPDATE_ID, update_result]))
ser.close()
# lockout should be either ARTOO_LOCKOUT_FALSE or ARTOO_LOCKOUT_TRUE
def setArtooLockout(lockout):
ser = serial.Serial("/dev/ttymxc1", 115200, timeout=1)
slipdev = slip.SlipDevice(ser)
slipdev.write("".join([ARTOO_LOCKOUT_ID, lockout]))
ser.close()
def mkdir_p(path):
try:
os.makedirs(path)
except OSError:
pass # already exists
def doUpdateComplete():
setArtooLockout(ARTOO_LOCKOUT_FALSE)
if not os.path.exists("/log/updates/READY"):
if os.path.exists("/log/updates/UPDATEFAILED"):
f = open("/log/updates/UPDATEFAILED", "r")
# file should be one line; read it all
r = f.read(1000)
f.close()
r = r.strip("\r\n\t\0 ")
logger.info("request \"update failed\" screen (%s)", r)
setArtooUpdateComplete(ARTOO_UPDATE_FAILED)
else:
logger.info("request \"update success\" screen")
setArtooUpdateComplete(ARTOO_UPDATE_SUCCESS)
mkdir_p("/log/updates")
open("/log/updates/READY", "w").close() # "touch"
else:
logger.info("no screen update (READY exists)")
# return tuple (filename, version), or None
def getFirmwareInfo():
files = sorted(glob.glob("/firmware/artoo_*.bin"))
if not files:
return None
filename = files[-1]
# Filename may be of the form
# "/firmware/artoo_0.0.0.bin", or
# "/firmware/artoo_v0.0.0.bin".
# Get it without the 'v'.
if filename[16] == 'v':
version = filename[17:-4]
else:
version = filename[16:-4]
return (filename, version)
# return version as string ("unknown" if can't get version)
def getArtooVersion():
#Check the version of the stm32 firmware over serial
#The STM32 might be emitting packets already, so we try a few times to get the
#version string. This has been observed to get the version with one retry on
#several occasions.
logger.info("requesting stm32 version")
version = "unknown"
ser = serial.Serial("/dev/ttymxc1", 115200, timeout=1)
slipdev = slip.SlipDevice(ser)
for i in range(5):
slipdev.write("".join([ARTOO_SYSINFO_ID]))
pkt = slipdev.read()
if not pkt:
logger.info("no data received from stm32, retrying")
continue
pkt = "".join(pkt)
if pkt[0] == ARTOO_SYSINFO_ID:
# SysInfo packet is: artoo/src/hostprotocol.cpp
# start size
# 0 1 ARTOO_SYSINFO_ID artoo/src/hostprotocol.h
# 1 12 UniqueId artoo/src/stm32/sys.h
# 13 2 hwversion artoo/src/hostprotocol.cpp
# 15 var Version artoo/src/version.h
# Version may start with an initial 'v', e.g. v0.6.10,
# but we want it starting with the numeric part.
if pkt[15] != 'v':
version = pkt[15:]
else:
version = pkt[16:]
break
logger.info("got %s/%d, retrying", str(hex(ord(pkt[0]))), len(pkt))
ser.close()
return version
# Attempt to update the STM32 multiple times
def updateStm32(filename):
# Total attempts will be retry+1, with the last one doing the unprotect.
# Since doing the unprotect results in a support call, retries is set
# such that the unprotect is a last resort. Allow for two init failures
# and two erase failures (truly degenerate) before doing the unprotect
# and (hopefully) fixing a protected stm32. Getting to the unprotect
# (where it is really needed) should happen only once in a controller's
# lifetime, if at all (it should never be needed, really).
stick_cal = None
retry = 4
while retry >= 0:
if retry > 0:
# don't unprotect chip the first few times an erase fails
logger.info("updating without readout protection disable")
if call_stm32loader(filename, disable_readout_protect=False):
break # Move on if the firmware is successfully loaded.
else:
# last attempt - unprotect chip if erase fails
logger.info("Updating the STM32 has failed several times. We suspect that readout protection is enabled.")
logger.info("Saving Stick Calibration: ")
stick_cal = read_stick_cal()
logger.info(stick_cal)
logger.info("updating with readout protection disable. This will wipe flash.")
call_stm32loader(filename, disable_readout_protect=True)
retry -= 1
logger.info("Update complete. Stick cal before update was: {}. (None means it wasn't read before update)".format(stick_cal))
logger.info("stick cal after update: {}".format(read_stick_cal()))
# Only write stick calibration if we did disabled readout protection:
if stick_cal is not None:
# Try to write_stick cal multiple times in case it fails.
# This is defensive. I've never seen it fail.
for i in range(3):
write_stick_cal(stick_cal)
time.sleep(1)
logger.info("Stick cal written. Reading it back again as a check:")
new_cal = read_stick_cal()
logger.info(new_cal)
if stick_cal == new_cal:
break
else:
logger.info("Stick cal failed to be written correctly:")
logger.info("Calibration attempted to be written: {}".format(stick_cal))
logger.info("Calibration found in flash: {}".format(new_cal))
# Initiate the stm32loader process to actually update the STM32 chip. Return True if successful.
def call_stm32loader(filename, disable_readout_protect=False):
# subprocess.check_output raises an exception if stm32loader.py returns a nonzero exit code for any reason
# including if readout_protect is enabled.
# Disable readout protect if specified. This will erase all flash.
if disable_readout_protect:
flags = "-wvqu"
else:
flags = "-wvq"
try:
s = subprocess.check_output(["stm32loader.py", flags, "-s", "127",
"-b", "115200", "-p", "/dev/ttymxc1", filename],
stderr=subprocess.STDOUT)
logger.info("stm32loader.py returned normally; output:")
success = True
except subprocess.CalledProcessError as cpe:
s = cpe.output
logger.info("stm32loader.py returned error; output:")
success = False
# this might be ugly, but it gets the output in the log
s = s.strip("\r\n\t\0 ")
logger.info(s)
# Wait a second for the STM32 to come back up before we send it a message later
time.sleep(1)
return success
def writeVersionFile(version):
f = open("/STM_VERSION", 'w')
f.write(version + '\n')
f.close()
# return version from /STM_VERSION
def getVersionFile():
try:
f = open("/STM_VERSION", 'r')
version = f.readline()
f.close()
version = version.strip()
if version == "":
version = "unknown"
except:
version = "unknown"
return version
def read_stick_cal():
# Start the STM32 process because sololink_config sets runlevel to 2 before an update,
# but we need the (runlevel 3) STM32 process to read a stick cal.
p = subprocess.Popen('stm32', shell=True)
time.sleep(3)
msg = param_stored_vals_msg.fetch() # String containing all artoo params (including stick cal values)
p.terminate() # Kill the STM32 process.
time.sleep(1) # Give time for the STM32 process to die. Not sure if needed.
# msg is an empty string if the stm32 process is not running (runlevel 2)
if not msg:
logger.info("Could not read stick calibration.")
return None
else:
msg = param_stored_vals_msg.unpack(msg) # Converts string to Dict
axes = msg.get('stickCals') # 6-item list of 3-item tuples
# Pack datastructure into a single list that can be written later
stick_cals = []
for a in axes:
stick_cals.extend(a)
return stick_cals
def write_stick_cal(values):
logger.info("Writing stick cal: {}".format(values))
serialpath = '/dev/ttymxc1'
serialport = serial.Serial(serialpath, ARTOO_BAUD)
slipdev = slip.SlipDevice(serialport)
packed_vals = struct.pack("<HHHHHHHHHHHHHHHHHH", *values)
slipdev.write("".join([ARTOO_CALIBRATE_ID, packed_vals]))
# Main
logging.config.fileConfig(sololink_conf)
logger = logging.getLogger("stm32")
logger.info("stm32 update starting")
firmware = getFirmwareInfo()
if firmware is None:
logger.info("no firmware available for update")
else:
logger.info("firmware: file %s, version %s", firmware[0], firmware[1])
# Read the version from the STM32
artoo_version = getArtooVersion()
logger.info("running version: %s", artoo_version)
# If we have firmware and it does not match what is running, update the STM32
if firmware is not None:
if artoo_version != firmware[1]:
logger.info("updating")
updateStm32(firmware[0])
# re-read the version from the running firmware
artoo_version = getArtooVersion()
else:
logger.info("not updating (new firmware is already running)")
# Whether we used it or not, we are done with the new firmware
logger.info("moving firmware to loaded")
mkdir_p("/firmware/loaded")
os.rename(firmware[0], "/firmware/loaded/" + os.path.basename(firmware[0]))
else:
logger.info("not updating (no new firmware)")
# Write version retrieved from STM32 to file
logger.info("writing STM_VERSION with running version %s", artoo_version)
writeVersionFile(artoo_version)
doUpdateComplete()
# delete /log/.factory if it exists (it has no effect)
if os.path.exists("/log/.factory"):
logger.info("deleting .factory")
os.system("rm -f /log/.factory")