esphome-flasher/esphomeflasher/common.py

202 lines
6.9 KiB
Python

import io
import struct
import esptool
from esphomeflasher.const import HTTP_REGEX
from esphomeflasher.helpers import prevent_print
class EsphomeflasherError(Exception):
pass
class MockEsptoolArgs(object):
def __init__(self, flash_size, addr_filename):
self.compress = True
self.no_compress = False
self.flash_size = flash_size
self.addr_filename = addr_filename
self.flash_mode = 'keep'
self.flash_freq = 'keep'
self.no_stub = False
self.verify = False
class ChipInfo(object):
def __init__(self, family, model, mac):
self.family = family
self.model = model
self.mac = mac
self.is_esp32 = None
def as_dict(self):
return {
'family': self.family,
'model': self.model,
'mac': self.mac,
'is_esp32': self.is_esp32,
}
class ESP32ChipInfo(ChipInfo):
def __init__(self, model, mac, num_cores, cpu_frequency, has_bluetooth, has_embedded_flash,
has_factory_calibrated_adc):
super(ESP32ChipInfo, self).__init__("ESP32", model, mac)
self.num_cores = num_cores
self.cpu_frequency = cpu_frequency
self.has_bluetooth = has_bluetooth
self.has_embedded_flash = has_embedded_flash
self.has_factory_calibrated_adc = has_factory_calibrated_adc
def as_dict(self):
data = ChipInfo.as_dict(self)
data.update({
'num_cores': self.num_cores,
'cpu_frequency': self.cpu_frequency,
'has_bluetooth': self.has_bluetooth,
'has_embedded_flash': self.has_embedded_flash,
'has_factory_calibrated_adc': self.has_factory_calibrated_adc,
})
return data
class ESP8266ChipInfo(ChipInfo):
def __init__(self, model, mac, chip_id):
super(ESP8266ChipInfo, self).__init__("ESP8266", model, mac)
self.chip_id = chip_id
def as_dict(self):
data = ChipInfo.as_dict(self)
data.update({
'chip_id': self.chip_id,
})
return data
def read_chip_property(func, *args, **kwargs):
try:
return prevent_print(func, *args, **kwargs)
except esptool.FatalError as err:
raise EsphomeflasherError("Reading chip details failed: {}".format(err))
def read_chip_info(chip):
mac = ':'.join('{:02X}'.format(x) for x in read_chip_property(chip.read_mac))
if isinstance(chip, esptool.ESP32ROM):
model = read_chip_property(chip.get_chip_description)
features = read_chip_property(chip.get_chip_features)
num_cores = 2 if 'Dual Core' in features else 1
frequency = next((x for x in ('160MHz', '240MHz') if x in features), '80MHz')
has_bluetooth = 'BT' in features
has_embedded_flash = 'Embedded Flash' in features
has_factory_calibrated_adc = 'VRef calibration in efuse' in features
return ESP32ChipInfo(model, mac, num_cores, frequency, has_bluetooth,
has_embedded_flash, has_factory_calibrated_adc)
elif isinstance(chip, esptool.ESP8266ROM):
model = read_chip_property(chip.get_chip_description)
chip_id = read_chip_property(chip.chip_id)
return ESP8266ChipInfo(model, mac, chip_id)
raise EsphomeflasherError("Unknown chip type {}".format(type(chip)))
def chip_run_stub(chip):
try:
return chip.run_stub()
except esptool.FatalError as err:
raise EsphomeflasherError("Error putting ESP in stub flash mode: {}".format(err))
def detect_flash_size(stub_chip):
flash_id = read_chip_property(stub_chip.flash_id)
return esptool.DETECTED_FLASH_SIZES.get(flash_id >> 16, '4MB')
def read_firmware_info(firmware):
header = firmware.read(4)
firmware.seek(0)
magic, _, flash_mode_raw, flash_size_freq = struct.unpack("BBBB", header)
if magic != esptool.ESPLoader.ESP_IMAGE_MAGIC:
raise EsphomeflasherError(
"The firmware binary is invalid (magic byte={:02X}, should be {:02X})"
"".format(magic, esptool.ESPLoader.ESP_IMAGE_MAGIC))
flash_freq_raw = flash_size_freq & 0x0F
flash_mode = {0: 'qio', 1: 'qout', 2: 'dio', 3: 'dout'}.get(flash_mode_raw)
flash_freq = {0: '40m', 1: '26m', 2: '20m', 0xF: '80m'}.get(flash_freq_raw)
return flash_mode, flash_freq
def open_downloadable_binary(path):
if hasattr(path, 'seek'):
path.seek(0)
return path
if HTTP_REGEX.match(path) is not None:
import requests
try:
response = requests.get(path)
response.raise_for_status()
except requests.exceptions.Timeout as err:
raise EsphomeflasherError(
"Timeout while retrieving firmware file '{}': {}".format(path, err))
except requests.exceptions.RequestException as err:
raise EsphomeflasherError(
"Error while retrieving firmware file '{}': {}".format(path, err))
binary = io.BytesIO()
binary.write(response.content)
binary.seek(0)
return binary
try:
return open(path, 'rb')
except IOError as err:
raise EsphomeflasherError("Error opening binary '{}': {}".format(path, err))
def format_bootloader_path(path, flash_mode, flash_freq):
return path.replace('$FLASH_MODE$', flash_mode).replace('$FLASH_FREQ$', flash_freq)
def configure_write_flash_args(info, firmware_path, flash_size,
bootloader_path, partitions_path, otadata_path):
addr_filename = []
firmware = open_downloadable_binary(firmware_path)
flash_mode, flash_freq = read_firmware_info(firmware)
if isinstance(info, ESP32ChipInfo):
if flash_freq in ('26m', '20m'):
raise EsphomeflasherError(
"No bootloader available for flash frequency {}".format(flash_freq))
bootloader = open_downloadable_binary(
format_bootloader_path(bootloader_path, flash_mode, flash_freq))
partitions = open_downloadable_binary(partitions_path)
otadata = open_downloadable_binary(otadata_path)
addr_filename.append((0x1000, bootloader))
addr_filename.append((0x8000, partitions))
addr_filename.append((0xE000, otadata))
addr_filename.append((0x10000, firmware))
else:
addr_filename.append((0x0, firmware))
return MockEsptoolArgs(flash_size, addr_filename)
def detect_chip(port, force_esp8266=False, force_esp32=False):
if force_esp8266 or force_esp32:
klass = esptool.ESP32ROM if force_esp32 else esptool.ESP8266ROM
chip = klass(port)
else:
try:
chip = esptool.ESPLoader.detect_chip(port)
except esptool.FatalError as err:
raise EsphomeflasherError("ESP Chip Auto-Detection failed: {}".format(err))
try:
chip.connect()
except esptool.FatalError as err:
raise EsphomeflasherError("Error connecting to ESP: {}".format(err))
return chip