mirror of https://github.com/arendst/Tasmota.git
134 lines
4.8 KiB
Plaintext
134 lines
4.8 KiB
Plaintext
|
# parse intelhex file
|
||
|
#
|
||
|
# use: `import intelhex`
|
||
|
#
|
||
|
# This class allows to open and parse an Intel HEX format file.
|
||
|
# Basic checks are done to make sure that the format is valid.
|
||
|
#
|
||
|
# If anything goes wrong, an exception is raised.
|
||
|
#
|
||
|
# You provide 3 callbacks:
|
||
|
# - pre: method called when file was opened, but content not parsed - no arg, no return (raise an exception if error)
|
||
|
# this is a good place to erase flash before writing
|
||
|
# - cb: method called for each line containing data to be flashed. args(address:int, len_in_bytes:int, data:bytes, offset:int)
|
||
|
# address: full address in Flash (high and low are automatically combined)
|
||
|
# len_in_bytes: length of the data line, generally max 32 bytes
|
||
|
# data: bytes() object containing raw bytes
|
||
|
# offset: offset in bytes() object, skipping first bytes without needing to reallocate buffer
|
||
|
# - post: method called when all data was parsed, and before the file is closed - no arg, no return (raise an exception if error)
|
||
|
# this is a good place to check CRC32 or any checksum
|
||
|
#
|
||
|
|
||
|
class intelhex
|
||
|
var filename # filename of hex file
|
||
|
var f # file object
|
||
|
var file_parsed # was the file already parsed. It cannot be flashed if not previously parsed and validated
|
||
|
var file_validated # was the file already validated. It cannot be flashed if not previously parsed and validated
|
||
|
|
||
|
def init(filename)
|
||
|
self.filename = str(filename)
|
||
|
self.file_parsed = false
|
||
|
self.file_validated = true
|
||
|
end
|
||
|
|
||
|
def close()
|
||
|
if self.f != nil
|
||
|
self.f.close()
|
||
|
self.f = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# open the intelhex file and parse from start to end
|
||
|
#
|
||
|
# Args:
|
||
|
# - pre: method called when file was opened, but content not parsed - no arg, no return (raise an exception if error)
|
||
|
# this is a good place to erase flash before writing
|
||
|
# - cb: method called for each line containing data to be flashed. args(address:int, len_in_bytes:int, data:bytes, offset:int)
|
||
|
# address: full address in Flash (high and low are automatically combined)
|
||
|
# len_in_bytes: length of the data line, generally max 32 bytes
|
||
|
# data: bytes() object containing raw bytes
|
||
|
# offset: offset in bytes() object, skipping first bytes without needing to reallocate buffer
|
||
|
# - post: method called when all data was parsed, and before the file is closed - no arg, no return (raise an exception if error)
|
||
|
# this is a good place to check CRC32 or any checksum
|
||
|
#
|
||
|
def parse(pre, parse_cb, post)
|
||
|
try
|
||
|
self.f = open(self.filename, "rb")
|
||
|
self.file_parsed = true # we tried to parse
|
||
|
pre()
|
||
|
|
||
|
self.munch_line(parse_cb)
|
||
|
|
||
|
post() # validated is computed internally
|
||
|
|
||
|
except .. as e, m
|
||
|
self.close()
|
||
|
self.file_validated = false
|
||
|
raise e, m # re-raise
|
||
|
end
|
||
|
|
||
|
self.close()
|
||
|
end
|
||
|
|
||
|
# internally used, verify each line
|
||
|
def munch_line(parse_cb)
|
||
|
import crc
|
||
|
var crc_sum = crc.sum
|
||
|
var tas = tasmota
|
||
|
var yield = tasmota.yield
|
||
|
|
||
|
var offset_high = 0
|
||
|
var offset_low = 0
|
||
|
var b = bytes()
|
||
|
var b_get = b.get
|
||
|
var b_fromhex = b.fromhex
|
||
|
var self_f = self.f
|
||
|
var readline = self_f.readline
|
||
|
while true
|
||
|
yield(tas) # tasmota.yield() -- faster version
|
||
|
var line = readline(self_f) # self.f.readline()
|
||
|
# print(line)
|
||
|
if line == "" raise "value_error", "unexpected end of file" end
|
||
|
if line[0] != ":" continue end # ignore empty line or not starting with ':'
|
||
|
|
||
|
b = b_fromhex(b, line, 1) # b.fromhex(line, 1) # convert to bytes, avoid allocating a new object
|
||
|
var sz = b[0]
|
||
|
|
||
|
# check size
|
||
|
if size(b) != sz+5 raise "value_error", "invalid size for line: "+line end
|
||
|
|
||
|
var record_type = b[3]
|
||
|
# 00: low address + data
|
||
|
# 01: end of file
|
||
|
# 04: high address
|
||
|
if record_type != 0 && record_type != 1 && record_type != 4
|
||
|
raise "value_error", "unsupported record_type: "+str(record_type)
|
||
|
end
|
||
|
|
||
|
offset_low = b_get(b, 1, -2) # b.get(1,-2)
|
||
|
var checksum = crc_sum(b)
|
||
|
if checksum != 0 raise "value_error", "invalid checksum" end
|
||
|
|
||
|
if record_type == 1 break end # end of file
|
||
|
if record_type == 0
|
||
|
# data
|
||
|
var address = offset_high << 16 | offset_low # full address
|
||
|
#var data = b[4..-2] # actual payload
|
||
|
parse_cb(address, sz, b, 4)
|
||
|
|
||
|
# OK
|
||
|
# do whatever needed
|
||
|
import string
|
||
|
# print(string.format("addr=0x%06X len=0x%02X", address, sz))
|
||
|
elif record_type == 4
|
||
|
if offset_low != 0 raise "value_error", "offset_low not null for cmd 04" end
|
||
|
offset_high = b_get(b, 4, -2) # b.get(4,-2)
|
||
|
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
return intelhex
|