class OTA: def __init__(self): self.part = Partition(Partition.RUNNING).get_next_update() self.sha = hashlib.sha256() self.seq = 0 self.block = 0 self.buf = bytearray(BLOCKLEN) self.buflen = 0 # handle processes one message with a chunk of data in msg. The sequence number seq needs # to increment sequentially and the last call needs to have last==True as well as the # sha set to the hashlib.sha256(entire_data).hexdigest(). def handle(self, sha, msg, seq, last): if self.seq is None: raise ValueError("missing first message") elif self.seq < seq: # "duplicate message" log.warning("Duplicate OTA message seq=%d", seq) return None elif self.seq > seq: raise ValueError("message missing") else: self.seq += 1 self.sha.update(msg) # avoid allocating memory: use buf as-is msglen = len(msg) if self.buflen + msglen >= BLOCKLEN: # got a full block, assemble it and write to flash cpylen = BLOCKLEN - self.buflen self.buf[self.buflen:BLOCKLEN] = msg[:cpylen] self.part.writeblocks(self.block, self.buf) self.block += 1 msglen -= cpylen if msglen > 0: self.buf[:msglen] = msg[cpylen:] self.buflen = msglen else: self.buf[self.buflen:self.buflen + msglen] = msg self.buflen += msglen if last and self.buflen > 0: for i in range(BLOCKLEN - self.buflen): self.buf[self.buflen + i] = 0xFF # erased flash is ff self.part.writeblocks(self.block, self.buf) self.block += 1 assert len(self.buf) == BLOCKLEN if last: return self.finish(sha) elif (seq & 7) == 0: # log.info("Sending ACK {}".format(seq)) return "SEQ {}".format(seq).encode() def finish(self, check_sha): del self.buf self.seq = None calc_sha = binascii.hexlify(self.sha.digest()) check_sha = check_sha.encode() if calc_sha != check_sha: raise ValueError("SHA mismatch calc:{} check={}".format( calc_sha, check_sha)) self.part.set_boot() return "OK"
class OTA: # constructor, follow by calling ota(...) def __init__(self, verbose=False): self.verbose = verbose # the partition we are writing to self.part = Partition(Partition.RUNNING).get_next_update() # sha of the new app, computed in _app_data self.sha = hashlib.sha256() # keeping track (_app_data) self.block = 0 self.buf = bytearray(BLOCKLEN) self.buflen = 0 # length of current content of self.buf # load app into the next partition and set it as the next one to boot upon restart # :param: url of app # :sha256: sha256 of app def ota(self, url, sha256): if sys.platform != 'esp32': raise ValueError("N/A") if self.verbose: print('OTA ', end='') buffer = bytearray(BLOCKLEN) mv = memoryview(buffer) sock = open_url(url) while True: sz = sock.readinto(buffer) if not sz: break self._app_data(mv[0:sz]) self._finish(sha256) buffer = None gc.collect() # accept chunks of the app and write to self.part def _app_data(self, data, last=False): global BLOCKLEN, buf, buflen, block data_len = len(data) self.sha.update(data) if self.buflen + data_len >= BLOCKLEN: # got a full block, assemble it and write to flash cpylen = BLOCKLEN - self.buflen self.buf[self.buflen:BLOCKLEN] = data[:cpylen] assert len(self.buf) == BLOCKLEN if self.verbose: print('.', end='') self.part.writeblocks(self.block, self.buf) self.block += 1 data_len -= cpylen if data_len > 0: self.buf[:data_len] = data[cpylen:] self.buflen = data_len else: self.buf[self.buflen:self.buflen + data_len] = data self.buflen += data_len if last and self.buflen > 0: for i in range(BLOCKLEN - self.buflen): self.buf[self.buflen + i] = 0xFF # ord('-') # erased flash is ff if self.verbose: print('.', end='') self.part.writeblocks(self.block, self.buf) assert len(self.buf) == BLOCKLEN # finish writing the app to the partition and check the sha def _finish(self, check_sha): # flush the app buffer and complete the write self._app_data(b'', last=False) self._app_data(b'', last=True) del self.buf # check the sha calc_sha = binascii.hexlify(self.sha.digest()) check_sha = check_sha.encode() if calc_sha != check_sha: raise ValueError( "SHA mismatch\n calc: {}\n check: {}".format( calc_sha, check_sha)) self.part.set_boot() if self.verbose: print(' Done.')