dfu_transport_mesh.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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 imports
  29. import time
  30. from datetime import datetime, timedelta
  31. import binascii
  32. import logging
  33. from threading import Thread, Lock
  34. import struct
  35. import random
  36. from collections import deque
  37. # Python 3rd party imports
  38. from serial import Serial
  39. # Nordic Semiconductor imports
  40. from nordicsemi.dfu.util import int16_to_bytes, int32_to_bytes, bytes_to_int32
  41. from nordicsemi.exceptions import NordicSemiException
  42. from nordicsemi.dfu.dfu_transport import DfuTransport, DfuEvent
  43. MESH_DFU_PACKET_FWID = 0xFFFE
  44. MESH_DFU_PACKET_STATE = 0xFFFD
  45. MESH_DFU_PACKET_DATA = 0xFFFC
  46. MESH_DFU_PACKET_DATA_REQ = 0xFFFB
  47. MESH_DFU_PACKET_DATA_RSP = 0xFFFA
  48. DFU_UPDATE_MODE_NONE = 0
  49. DFU_UPDATE_MODE_SD = 1
  50. DFU_UPDATE_MODE_BL = 2
  51. DFU_UPDATE_MODE_APP = 4
  52. logger = logging.getLogger(__name__)
  53. class DfuVersion:
  54. def __init__(self, sd=None, bl_id=None, bl_ver=None, company_id=None, app_id=None, app_ver=None):
  55. self.sd = sd
  56. self.bl_id = bl_id
  57. self.bl_ver = bl_ver
  58. self.company_id = company_id
  59. self.app_id = app_id
  60. self.app_ver = app_ver
  61. def get_number(self, dfu_type):
  62. if dfu_type == DFU_UPDATE_MODE_SD:
  63. if self.sd is None:
  64. raise ValueError("sd can't be None if type is SD")
  65. return int16_to_bytes(self.sd)
  66. elif dfu_type == DFU_UPDATE_MODE_BL:
  67. if self.bl_id is None:
  68. raise ValueError("bl_id can't be None if type is BL")
  69. if self.bl_ver is None:
  70. raise ValueError("bl_ver can't be None if type is BL")
  71. number = ''
  72. number += chr(self.bl_id)
  73. number += chr(self.bl_ver)
  74. return number
  75. elif dfu_type == DFU_UPDATE_MODE_APP:
  76. if self.company_id is None:
  77. raise ValueError("company_id can't be None if type is APP")
  78. if self.app_id is None:
  79. raise ValueError("app_id can't be None if type is APP")
  80. if self.app_ver is None:
  81. raise ValueError("app_ver can't be None if type is APP")
  82. number = ''
  83. number += int32_to_bytes(self.company_id)
  84. number += int16_to_bytes(self.app_id)
  85. number += int32_to_bytes(self.app_ver)
  86. return number
  87. else:
  88. print "UNABLE TO GET DFU NUMBER WITH TYPE {0}".format(ord(dfu_type))
  89. return None
  90. def is_larger_than(self, other, dfu_type):
  91. if dfu_type == DFU_UPDATE_MODE_SD:
  92. return False
  93. elif dfu_type == DFU_UPDATE_MODE_BL:
  94. if self.bl_id != other.bl_id or self.bl_ver <= other.bl_ver:
  95. return False
  96. elif dfu_type == DFU_UPDATE_MODE_APP:
  97. if self.company_id != other.company_id or self.app_id != other.app_id or self.app_ver <= other.app_ver:
  98. return False
  99. else:
  100. return False
  101. return True
  102. def __str__(self):
  103. number = ''
  104. number += self.get_number(DFU_UPDATE_MODE_SD)
  105. number += self.get_number(DFU_UPDATE_MODE_BL)
  106. number += self.get_number(DFU_UPDATE_MODE_APP)
  107. return binascii.hexlify(number)
  108. class DfuInfoMesh:
  109. def __init__(self, data):
  110. self.dfu_type = ord(data[0])
  111. self.start_addr = bytes_to_int32(data[1:5])
  112. self.fw_len = bytes_to_int32(data[5:9])
  113. if len(data) > 64:
  114. self.sign_len = ord(data[9])
  115. self.signature = data[10:10 + self.sign_len]
  116. else:
  117. self.sign_len = 0
  118. raw_ver = data[10 + self.sign_len:]
  119. self.ver = DfuVersion(
  120. sd = bytes_to_int32(raw_ver[0:2]),
  121. bl_id = ord(raw_ver[0]),
  122. bl_ver = ord(raw_ver[1]),
  123. company_id = bytes_to_int32(raw_ver[0:4]),
  124. app_id = bytes_to_int32(raw_ver[4:6]),
  125. app_ver = bytes_to_int32(raw_ver[6:10]))
  126. class DfuTransportMesh(DfuTransport):
  127. DEFAULT_BAUD_RATE = 1000000
  128. DEFAULT_FLOW_CONTROL = True
  129. DEFAULT_SERIAL_PORT_TIMEOUT = 5.0 # Timeout time on serial port read
  130. SEND_START_DFU_WAIT_TIME = 2.0 # Time to wait before communicating with bootloader after start DFU packet is sent
  131. SEND_DATA_PACKET_WAIT_TIME = 0.5 # Time between each data packet
  132. DFU_PACKET_MAX_SIZE = 16 # The DFU packet max size
  133. ACK_WAIT_TIME = 0.5 # Time to wait for an ack before attempting to resend a packet.
  134. DATA_REQ_WAIT_TIME = 10.0 # Time to wait for missing packet requests after sending all packets
  135. MAX_CONTINUOUS_MESSAGE_INTERBYTE_GAP = 0.1 # Maximal time to wait between two bytes in the same packet
  136. MAX_RETRIES = 10 # Number of send retries before the serial connection is considered lost.
  137. # Worst case page erase time (max time + max time * worst case % of HFINT accuracy):
  138. # 52832: 89.7 + 6%: 95.08 ms; 52833: 87.5 + 9%: 88.59 ms; 52840: 85 + 8% : 86.08
  139. PAGE_ERASE_TIME_MAX = 95.08/1000
  140. PAGE_SIZE = 4096
  141. def __init__(self, com_port, baud_rate=DEFAULT_BAUD_RATE, flow_control=DEFAULT_FLOW_CONTROL, timeout=DEFAULT_SERIAL_PORT_TIMEOUT, interval=SEND_DATA_PACKET_WAIT_TIME):
  142. super(DfuTransportMesh, self).__init__()
  143. self.com_port = com_port
  144. self.baud_rate = baud_rate
  145. self.flow_control = 1 if flow_control else 0
  146. self.timeout = timeout
  147. self.write_lock = Lock()
  148. self.serial_port = None
  149. """:type: serial.Serial """
  150. self.pending_packets = []
  151. self.packet_handlers = {}
  152. self.packet_handlers['\x78'] = self._handle_dfu
  153. self.packet_handlers['\x81'] = self._handle_started
  154. self.packet_handlers['\x82'] = self._handle_echo
  155. self.packet_handlers['\x84'] = self._handle_ack
  156. self.requested_packets = deque()
  157. self.info = None
  158. self.tid = 0
  159. self.firmware = None
  160. self.rxthread = None
  161. self.interval = interval
  162. def open(self):
  163. super(DfuTransportMesh, self).open()
  164. try:
  165. self.serial_port = Serial(port=self.com_port, baudrate=self.baud_rate, rtscts=self.flow_control, timeout=DfuTransportMesh.MAX_CONTINUOUS_MESSAGE_INTERBYTE_GAP)
  166. except Exception, e:
  167. if self.serial_port:
  168. self.serial_port.close()
  169. raise NordicSemiException("Serial port could not be opened on {0}. Reason: {1}".format(self.com_port, e.message))
  170. self.switch_dfu()
  171. self.serial_port.close()
  172. try:
  173. self.serial_port = Serial(port=self.com_port, baudrate=self.baud_rate, rtscts=self.flow_control, timeout=DfuTransportMesh.MAX_CONTINUOUS_MESSAGE_INTERBYTE_GAP)
  174. except Exception, e:
  175. if self.serial_port:
  176. self.serial_port.close()
  177. raise NordicSemiException("Serial port could not be opened on {0}. Reason: {1}".format(self.com_port, e.message))
  178. # Flush out-buffer
  179. logger.info("Flushing com-port...")
  180. # Flush incoming data by the assumption that no continuous message has a time gap of >MAX_CONTINUOUS_MESSAGE_INTERBYTE_GAP seconds
  181. while len(self.serial_port.read()) > 0:
  182. pass
  183. self.serial_port.timeout = self.timeout # set the timeout to actually wanted value
  184. logger.info("PACOM: Opened com-port")
  185. self.rxthread = Thread(target=self.receive_thread)
  186. self.rxthread.daemon = False
  187. self.rxthread.start()
  188. time.sleep(1)
  189. def __del__(self):
  190. self.close()
  191. def close(self):
  192. logger.info("Closing serial port...")
  193. if self.is_open():
  194. self.serial_port.close()
  195. if self.rxthread:
  196. self.rxthread.join()
  197. def is_open(self):
  198. if self.serial_port is None:
  199. return False
  200. return self.serial_port.isOpen()
  201. def send_validate_firmware(self):
  202. super(DfuTransportMesh, self).send_validate_firmware()
  203. return True
  204. def send_start_dfu(self, mode, softdevice_size=None, bootloader_size=None, app_size=None):
  205. super(DfuTransportMesh, self).send_start_dfu(mode, softdevice_size, bootloader_size, app_size)
  206. # send echo for testing
  207. echo_packet = SerialPacket('\xAA\xBB\xCC\xDD', opcode=0x02)
  208. self.send_packet(echo_packet)
  209. def send_init_packet(self, init_packet):
  210. # send all init packets with reasonable delay
  211. super(DfuTransportMesh, self).send_init_packet(init_packet)
  212. self.info = DfuInfoMesh(init_packet)
  213. self.tid = random.randint(0, 0xFFFFFFFF)
  214. ready = ''
  215. ready += int16_to_bytes(MESH_DFU_PACKET_STATE)
  216. ready += chr(self.info.dfu_type)
  217. ready += chr(0x0F)
  218. ready += int32_to_bytes(self.tid)
  219. ready += self.info.ver.get_number(self.info.dfu_type)
  220. ready_packet = SerialPacket(ready)
  221. logger.info("Sending ready packet")
  222. self.send_packet(ready_packet)
  223. time.sleep(DfuTransportMesh.SEND_START_DFU_WAIT_TIME)
  224. # send twice to ensure the application catches the TID.
  225. self.send_packet(ready_packet)
  226. time.sleep(DfuTransportMesh.SEND_START_DFU_WAIT_TIME)
  227. start_data = ''
  228. start_data += int16_to_bytes(MESH_DFU_PACKET_DATA)
  229. start_data += '\x00\x00'
  230. start_data += int32_to_bytes(self.tid)
  231. start_data += int32_to_bytes(self.info.start_addr)
  232. start_data += int32_to_bytes(self.info.fw_len / 4)
  233. start_data += int16_to_bytes(self.info.sign_len)
  234. start_data += '\x0C'
  235. start_packet = SerialPacket(start_data)
  236. logger.info("Sending start packet")
  237. self.send_packet(start_packet)
  238. # Wait time: time to erase flash + 50% margin for stack operation and timeslots if GATT is connected
  239. wait_time = DfuTransportMesh.PAGE_ERASE_TIME_MAX * (self.info.fw_len / DfuTransportMesh.PAGE_SIZE) * 1.50
  240. logger.info("Waiting for %.1f seconds for flash bank erase to complete." % wait_time)
  241. time.sleep(wait_time)
  242. def send_activate_firmware(self):
  243. super(DfuTransportMesh, self).send_activate_firmware()
  244. def send_firmware(self, firmware):
  245. super(DfuTransportMesh, self).send_firmware(firmware)
  246. self.firmware = firmware
  247. frames = []
  248. self._send_event(DfuEvent.PROGRESS_EVENT, progress=0, done=False, log_message="")
  249. fw_segments = len(firmware) / DfuTransportMesh.DFU_PACKET_MAX_SIZE
  250. if len(firmware) % DfuTransportMesh.DFU_PACKET_MAX_SIZE > 0:
  251. fw_segments += 1
  252. for segment in range(1, 1 + fw_segments):
  253. data_packet = ''
  254. data_packet += int16_to_bytes(MESH_DFU_PACKET_DATA)
  255. data_packet += int16_to_bytes(segment)
  256. data_packet += int32_to_bytes(self.tid)
  257. data_packet += self.get_fw_segment(segment)
  258. frames.append(data_packet)
  259. # add signature at the end
  260. for (segment, i) in enumerate(range(0, self.info.sign_len, DfuTransportMesh.DFU_PACKET_MAX_SIZE)):
  261. sign_packet = ''
  262. sign_packet += int16_to_bytes(MESH_DFU_PACKET_DATA)
  263. sign_packet += int16_to_bytes(segment + fw_segments + 1)
  264. sign_packet += int32_to_bytes(self.tid)
  265. if i >= self.info.sign_len:
  266. sign_packet += self.info.signature[i:]
  267. else:
  268. sign_packet += self.info.signature[i:i + DfuTransportMesh.DFU_PACKET_MAX_SIZE]
  269. frames.append(sign_packet)
  270. frames_count = len(frames)
  271. # Send firmware packets
  272. self.temp_progress = 0.0
  273. for (count, pkt) in enumerate(frames):
  274. # First resend any requested packets
  275. while len(self.requested_packets) > 0:
  276. self.send_packet(SerialPacket(self.requested_packets.popleft()))
  277. time.sleep(self.interval)
  278. # Then send next frame
  279. self.send_packet(SerialPacket(pkt))
  280. self.log_progress(100.0 / float(frames_count))
  281. time.sleep(self.interval)
  282. # Wait for any final missing packet requests
  283. time.sleep(DfuTransportMesh.DATA_REQ_WAIT_TIME)
  284. while len(self.requested_packets) > 0:
  285. self.send_packet(SerialPacket(self.requested_packets.popleft()))
  286. time.sleep(self.interval)
  287. while len(self.pending_packets) > 0:
  288. time.sleep(0.01)
  289. self._send_event(DfuEvent.PROGRESS_EVENT, progress=100, done=True, log_message="")
  290. def log_progress(self, progress):
  291. self.temp_progress += progress
  292. if self.temp_progress > 1.0:
  293. self._send_event(DfuEvent.PROGRESS_EVENT,
  294. log_message="",
  295. progress= self.temp_progress,
  296. done=False)
  297. self.temp_progress = 0.0
  298. def get_fw_segment(self, segment):
  299. i = (segment - 1) * DfuTransportMesh.DFU_PACKET_MAX_SIZE
  300. if segment is 1 and self.info.start_addr != 0xFFFFFFFF:
  301. # first packet must normalize 16-byte alignment
  302. return self.firmware[i:i + 16 - (self.info.start_addr % 16)]
  303. elif i >= len(self.firmware):
  304. return None
  305. elif i + DfuTransportMesh.DFU_PACKET_MAX_SIZE > len(self.firmware):
  306. return self.firmware[i:]
  307. else:
  308. return self.firmware[i:i + DfuTransportMesh.DFU_PACKET_MAX_SIZE]
  309. def send_packet(self, pkt):
  310. wait_time = DfuTransportMesh.ACK_WAIT_TIME
  311. self.pending_packets.append(pkt)
  312. retries = 0
  313. while retries < DfuTransportMesh.MAX_RETRIES and pkt in self.pending_packets:
  314. logger.info(str(retries + 1) + ": PC -> target: " + binascii.hexlify(pkt.data))
  315. self.serial_port.write(pkt.data)
  316. timeout = wait_time + time.clock()
  317. while pkt in self.pending_packets and time.clock() < timeout:
  318. time.sleep(0.01)
  319. retries += 1
  320. if retries == DfuTransportMesh.MAX_RETRIES:
  321. raise Exception(pkt.fail_reason)
  322. def switch_dfu(self):
  323. logger.info("Switch DFU mode")
  324. cmd = [0xfe, 0x01, 0xc3, 0x02, 0x7b, 0x7d, 0x90, 0xff, 0x00]
  325. self.serial_port.write(cmd)
  326. time.sleep(0.3)
  327. def reboot_dut(self):
  328. logger.info("Reboot serial device")
  329. cmd = [0xfe, 0x01, 0x79, 0x35, 0x7b, 0x22, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x3a,
  330. 0x7b, 0x22, 0x49, 0x44, 0x22, 0x3a, 0x34, 0x31, 0x37, 0x32, 0x2c, 0x22, 0x4d, 0x6f, 0x64,
  331. 0x65, 0x6c, 0x22, 0x3a, 0x7b, 0x22, 0x49, 0x44, 0x22, 0x3a, 0x34, 0x30, 0x39, 0x36, 0x2c,
  332. 0x22, 0x6f, 0x6e, 0x6f, 0x66, 0x66, 0x22, 0x3a, 0x31, 0x7d, 0x7d, 0x7d, 0x94, 0xff, 0x00 ]
  333. self.serial_port.write(cmd)
  334. time.sleep(10)
  335. def receive_packet(self):
  336. if self.serial_port and self.serial_port.isOpen():
  337. packet_len = self.serial_port.read(1)
  338. if packet_len:
  339. packet_len = ord(packet_len)
  340. if packet_len > 0:
  341. rx_data = self.serial_port.read(packet_len)
  342. logger.info("target -> PC: " + format(packet_len, '02x') + binascii.hexlify(rx_data))
  343. return rx_data
  344. return None
  345. def receive_thread(self):
  346. try:
  347. while self.is_open():
  348. rx_data = self.receive_packet()
  349. if rx_data and rx_data[0] in self.packet_handlers:
  350. self.packet_handlers[rx_data[0]](rx_data)
  351. except:
  352. pass
  353. def send_bytes(self, data):
  354. with self.write_lock:
  355. logger.info("PC -> target: " + binascii.hexlify(data))
  356. self.serial_port.write(data)
  357. def push_timeout(self):
  358. self._send_event(DfuEvent.TIMEOUT_EVENT,
  359. log_message="Timed out waiting for acknowledgement from device.")
  360. ############### PACKET HANDLERS
  361. def _handle_dfu(self, data):
  362. handle = bytes_to_int32(data[1:3])
  363. if handle == MESH_DFU_PACKET_DATA_REQ:
  364. self._handle_dfu_data_req(data[3:])
  365. def _handle_echo(self, data):
  366. for packet in self.pending_packets:
  367. if packet.get_opcode() == 0x02:
  368. self.pending_packets.remove(packet)
  369. logger.info("Got echo response")
  370. def _handle_ack(self, data):
  371. for packet in self.pending_packets:
  372. if packet.check_ack(data):
  373. self.pending_packets.remove(packet)
  374. def _handle_started(self, data):
  375. pass
  376. def _handle_dfu_data_req(self, data):
  377. segment = bytes_to_int32(data[0:2])
  378. tid = bytes_to_int32(data[2:6])
  379. if (tid == self.tid) and (self.firmware is not None) and (segment > 0):
  380. rsp = ''
  381. rsp += int16_to_bytes(MESH_DFU_PACKET_DATA_RSP)
  382. rsp += data[:6]
  383. fw_segment = self.get_fw_segment(segment)
  384. if not fw_segment:
  385. return # invalid segment number
  386. rsp += fw_segment
  387. self.requested_packets.append(rsp)
  388. def get_longest_matching(lst, data):
  389. i = max([k for k in lst if data.startswith(k)], key=lambda k: len(k))
  390. if i in lst:
  391. return lst[i]
  392. else:
  393. return None
  394. class SerialPacket(object):
  395. FAIL_REASON = {
  396. '\x02': 'Failed to establish connection',
  397. '\x78\xFA\xFF': 'Lost connection in the middle of responding to a missing packet request',
  398. '\x78\xFC\xFF\x00\x00': 'Crashed on start packet',
  399. '\x78\xFC\xFF': 'Lost connection in the middle of the transfer',
  400. '\x78\xFD\xFF': 'Lost connection in the setup phase',
  401. '\x78\xFE\xFF': 'Lost connection before starting the transfer'
  402. }
  403. SERIAL_STATUS_CODES={
  404. 0x00: 'SUCCESS',
  405. 0x80: 'ERROR_UNKNOWN',
  406. 0x81: 'ERROR_INTERNAL',
  407. 0x82: 'ERROR_CMD_UNKNOWN',
  408. 0x83: 'ERROR_DEVICE_STATE_INVALID',
  409. 0x84: 'ERROR_INVALID_LENGTH',
  410. 0x85: 'ERROR_INVALID_PARAMETER',
  411. 0x86: 'ERROR_BUSY',
  412. 0x87: 'ERROR_INVALID_DATA',
  413. 0x90: 'ERROR_PIPE_INVALID'
  414. }
  415. SERIAL_OPCODES={
  416. '\x02': 'Echo',
  417. '\x0E': 'Radio reset',
  418. '\x70': 'Init',
  419. '\x71': 'Value set',
  420. '\x72': 'Value enable',
  421. '\x73': 'Value disable',
  422. '\x74': 'Start',
  423. '\x75': 'Stop',
  424. '\x76': 'Flag set',
  425. '\x77': 'Flag get',
  426. '\x78\xFE': 'DFU FWID beacon',
  427. '\x78\xFD': 'DFU state beacon',
  428. '\x78\xFC\x00\x00': 'DFU start',
  429. '\x78\xFC': 'DFU data',
  430. '\x78\xFA': 'DFU data response',
  431. '\x7A': 'Value get',
  432. '\x7B': 'Build version get',
  433. '\x7C': 'Access addr get',
  434. '\x7D': 'Channel get',
  435. '\x7F': 'Interval get'
  436. }
  437. """Class representing a single Mesh serial packet"""
  438. def __init__(self, data='', opcode=0x78):
  439. self.data = ''
  440. self.data += chr(len(data) + 1)
  441. self.data += chr(opcode)
  442. self.data += data
  443. self.packet_name = get_longest_matching(SerialPacket.SERIAL_OPCODES, self.data[1:])
  444. self.fail_reason = get_longest_matching(SerialPacket.FAIL_REASON, self.data[1:])
  445. def get_opcode(self):
  446. return ord(self.data[1])
  447. def get_type(self):
  448. temp = '\x00\x00' + self.data[2:4]
  449. return (struct.unpack("<L", self.data[2:4] + '\x00\x00')[0])
  450. def check_ack(self, ack_data):
  451. error_code = ord(ack_data[2])
  452. for_me = ((ack_data[0] == '\x84') and (ack_data[1] == self.data[1]))
  453. acked = (for_me and (error_code == 0x00))
  454. if for_me and error_code != 0:
  455. if error_code in SerialPacket.SERIAL_STATUS_CODES:
  456. self.fail_reason = 'Device returned status code ' + SerialPacket.SERIAL_STATUS_CODES[error_code] + ' (' + str(error_code) + ') on a ' + self.packet_name + ' packet.'
  457. else:
  458. self.fail_reason = 'Device returned an unknown status code (' + str(error_code) + ') on a ' + self.packet_name + ' packet.'
  459. return acked
  460. def __str__(self):
  461. return binascii.hexlify(self.data)