dfu.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # Copyright (c) 2015, Nordic Semiconductor
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are met:
  6. #
  7. # * Redistributions of source code must retain the above copyright notice, this
  8. # list of conditions and the following disclaimer.
  9. #
  10. # * Redistributions in binary form must reproduce the above copyright notice,
  11. # this list of conditions and the following disclaimer in the documentation
  12. # and/or other materials provided with the distribution.
  13. #
  14. # * Neither the name of Nordic Semiconductor ASA nor the names of its
  15. # contributors may be used to endorse or promote products derived from
  16. # this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  19. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  20. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  22. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  23. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  24. # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  25. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  26. # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  27. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. # Python standard library
  29. import os
  30. import tempfile
  31. import shutil
  32. import logging
  33. from time import time, sleep
  34. from datetime import datetime, timedelta
  35. # Nordic libraries
  36. from nordicsemi.exceptions import *
  37. from nordicsemi.dfu.package import Package
  38. from nordicsemi.dfu.dfu_transport import DfuEvent
  39. from nordicsemi.dfu.model import HexType
  40. from nordicsemi.dfu.manifest import SoftdeviceBootloaderFirmware
  41. logger = logging.getLogger(__name__)
  42. class Dfu(object):
  43. """ Class to handle upload of a new hex image to the device. """
  44. def __init__(self, zip_file_path, dfu_transport):
  45. """
  46. Initializes the dfu upgrade, unpacks zip and registers callbacks.
  47. @param zip_file_path: Path to the zip file with the firmware to upgrade
  48. @type zip_file_path: str
  49. @param dfu_transport: Transport backend to use to upgrade
  50. @type dfu_transport: nordicsemi.dfu.dfu_transport.DfuTransport
  51. @return
  52. """
  53. self.zip_file_path = zip_file_path
  54. self.ready_to_send = True
  55. self.response_opcode_received = None
  56. self.temp_dir = tempfile.mkdtemp(prefix="nrf_dfu_")
  57. self.unpacked_zip_path = os.path.join(self.temp_dir, 'unpacked_zip')
  58. self.manifest = Package.unpack_package(self.zip_file_path, self.unpacked_zip_path)
  59. if dfu_transport:
  60. self.dfu_transport = dfu_transport
  61. self.dfu_transport.register_events_callback(DfuEvent.TIMEOUT_EVENT, self.timeout_event_handler)
  62. self.dfu_transport.register_events_callback(DfuEvent.ERROR_EVENT, self.error_event_handler)
  63. def __del__(self):
  64. """
  65. Destructor removes the temporary directory for the unpacked zip
  66. :return:
  67. """
  68. shutil.rmtree(self.temp_dir)
  69. def error_event_handler(self, log_message=""):
  70. """
  71. Event handler for errors, closes the transport backend.
  72. :param str log_message: The log message for the error.
  73. :return:
  74. """
  75. if self.dfu_transport.is_open():
  76. self.dfu_transport.close()
  77. logger.error(log_message)
  78. def timeout_event_handler(self, log_message):
  79. """
  80. Event handler for timeouts, closes the transport backend.
  81. :param log_message: The log message for the timeout.
  82. :return:
  83. """
  84. if self.dfu_transport.is_open():
  85. self.dfu_transport.close()
  86. logger.error(log_message)
  87. @staticmethod
  88. def _read_file(file_path):
  89. """
  90. Reads a file and returns the content as a string.
  91. :param str file_path: The path to the file to read.
  92. :return str: Content of the file.
  93. """
  94. buffer_size = 4096
  95. file_content = ""
  96. with open(file_path, 'rb') as binary_file:
  97. while True:
  98. data = binary_file.read(buffer_size)
  99. if data:
  100. file_content += data
  101. else:
  102. break
  103. return file_content
  104. def _wait_while_opening_transport(self):
  105. timeout = 10
  106. start_time = datetime.now()
  107. while not self.dfu_transport.is_open():
  108. timed_out = datetime.now() - start_time > timedelta(0, timeout)
  109. if timed_out:
  110. log_message = "Failed to open transport backend"
  111. raise NordicSemiException(log_message)
  112. sleep(0.1)
  113. def _dfu_send_image(self, program_mode, firmware_manifest):
  114. """
  115. Does DFU for one image. Reads the firmware image and init file.
  116. Opens the transport backend, calls setup, send and finalize and closes the backend again.
  117. @param program_mode: What type of firmware the DFU is
  118. @type program_mode: nordicsemi.dfu.model.HexType
  119. @param firmware_manifest: The manifest for the firmware image
  120. @type firmware_manifest: nordicsemi.dfu.manifest.Firmware
  121. @return:
  122. """
  123. if firmware_manifest is None:
  124. raise MissingArgumentException("firmware_manifest must be provided.")
  125. if self.dfu_transport.is_open():
  126. raise IllegalStateException("Transport is already open.")
  127. self.dfu_transport.open()
  128. self._wait_while_opening_transport()
  129. softdevice_size = 0
  130. bootloader_size = 0
  131. application_size = 0
  132. bin_file_path = os.path.join(self.unpacked_zip_path, firmware_manifest.bin_file)
  133. firmware = self._read_file(bin_file_path)
  134. dat_file_path = os.path.join(self.unpacked_zip_path, firmware_manifest.dat_file)
  135. init_packet = self._read_file(dat_file_path)
  136. if program_mode == HexType.SD_BL:
  137. if not isinstance(firmware_manifest, SoftdeviceBootloaderFirmware):
  138. raise NordicSemiException("Wrong type of manifest")
  139. softdevice_size = firmware_manifest.sd_size
  140. bootloader_size = firmware_manifest.bl_size
  141. firmware_size = len(firmware)
  142. if softdevice_size + bootloader_size != firmware_size:
  143. raise NordicSemiException(
  144. "Size of bootloader ({} bytes) and softdevice ({} bytes)"
  145. " is not equal to firmware provided ({} bytes)".format(
  146. bootloader_size, softdevice_size, firmware_size))
  147. elif program_mode == HexType.SOFTDEVICE:
  148. softdevice_size = len(firmware)
  149. elif program_mode == HexType.BOOTLOADER:
  150. bootloader_size = len(firmware)
  151. elif program_mode == HexType.APPLICATION:
  152. application_size = len(firmware)
  153. start_time = time()
  154. logger.info("Starting DFU upgrade of type %s, SoftDevice size: %s, bootloader size: %s, application size: %s",
  155. program_mode,
  156. softdevice_size,
  157. bootloader_size,
  158. application_size)
  159. logger.info("Sending DFU start packet, afterwards we wait for the flash on "
  160. "target to be initialized before continuing.")
  161. self.dfu_transport.send_start_dfu(program_mode, softdevice_size, bootloader_size,
  162. application_size)
  163. logger.info("Sending DFU init packet")
  164. self.dfu_transport.send_init_packet(init_packet)
  165. logger.info("Sending firmware file")
  166. self.dfu_transport.send_firmware(firmware)
  167. self.dfu_transport.send_validate_firmware()
  168. self.dfu_transport.send_activate_firmware()
  169. end_time = time()
  170. logger.info("DFU upgrade took {0}s".format(end_time - start_time))
  171. self.dfu_transport.switch_normal()
  172. self.dfu_transport.close()
  173. def dfu_send_images(self):
  174. """
  175. Does DFU for all firmware images in the stored manifest.
  176. :return:
  177. """
  178. if self.manifest.softdevice_bootloader:
  179. self._dfu_send_image(HexType.SD_BL, self.manifest.softdevice_bootloader)
  180. if self.manifest.softdevice:
  181. self._dfu_send_image(HexType.SOFTDEVICE, self.manifest.softdevice)
  182. if self.manifest.bootloader:
  183. self._dfu_send_image(HexType.BOOTLOADER, self.manifest.bootloader)
  184. if self.manifest.application:
  185. self._dfu_send_image(HexType.APPLICATION, self.manifest.application)