@@ -106,13 +106,20 @@ def _send_command(self, command: Command, payload: Optional[bytes] = None) -> in
106106
107107 return message_number
108108
109- def _receive_reply (self , command : Command , message_number : int ) -> bytes :
109+ def _receive_reply (
110+ self , command : Command , message_number : int , force_length : int = 0
111+ ) -> bytes :
110112 """
111113 Receive a reply from the EV3 bootloader.
112114
113115 Args:
114116 command: The command that was sent.
115117 message_number: The return value of :meth:`_send_command`.
118+ force_length: Expected length, used only when it fails to unpack
119+ normally. Some replies on USB 3.0 hosts contain
120+ the original command written over the reply. This
121+ means the header is bad, but the payload may be in
122+ tact if you know what data to expect.
116123
117124 Returns:
118125 The payload of the reply.
@@ -131,36 +138,41 @@ def _receive_reply(self, command: Command, message_number: int) -> bytes:
131138 raise ReplyError (status )
132139
133140 if message_type != MessageType .SYSTEM_REPLY :
134- raise RuntimeError ("unexpected message type: {message_type}" )
141+ if force_length :
142+ return reply [7 : force_length + 2 ]
143+ raise RuntimeError (f"unexpected message type: { message_type } " )
135144
136145 if reply_command != command :
137- raise RuntimeError ("command mismatch: {reply_command} != {command}" )
146+ raise RuntimeError (f "command mismatch: { reply_command } != { command } " )
138147
139148 return reply [7 : length + 2 ]
140149
141150 def download_sync (
142151 self ,
143- address : int ,
144152 data : bytes ,
145153 progress : Optional [Callable [[int ], None ]] = None ,
146154 ) -> None :
147155 """
148156 Blocking version of :meth:`download`.
149157 """
150- param_data = struct .pack ("<II" , address , len (data ))
151- num = self ._send_command (Command .BEGIN_DOWNLOAD , param_data )
152- self ._receive_reply (Command .BEGIN_DOWNLOAD , num )
153158
159+ completed = 0
154160 for c in chunk (data , self ._MAX_DATA_SIZE ):
155161 num = self ._send_command (Command .DOWNLOAD_DATA , c )
156- self ._receive_reply (Command .DOWNLOAD_DATA , num )
162+ try :
163+ completed += len (c )
164+ self ._receive_reply (Command .DOWNLOAD_DATA , num )
165+ except RuntimeError as e :
166+ # Allow exception only on the final chunk.
167+ if completed != len (data ):
168+ raise e
169+ print (e , ". Proceeding anyway." )
157170
158171 if progress :
159172 progress (len (c ))
160173
161174 async def download (
162175 self ,
163- address : int ,
164176 data : bytes ,
165177 progress : Optional [Callable [[int ], None ]] = None ,
166178 ) -> None :
@@ -170,30 +182,31 @@ async def download(
170182 This operation takes about 60 seconds for a full 16MB firmware file.
171183
172184 Args:
173- address: The starting address of where to write the data.
174185 data: The data to write.
175186 progress: Optional callback for indicating progress.
176187 """
177188 return await asyncio .get_running_loop ().run_in_executor (
178- None , self .download_sync , address , data , progress
189+ None , self .download_sync , data , progress
179190 )
180191
181- def erase_chip_sync (self ) -> None :
192+ def erase_and_begin_download_sync (self , size ) -> None :
182193 """
183- Blocking version of :meth:`erase_chip `.
194+ Blocking version of :meth:`erase_and_begin_download `.
184195 """
185- num = self ._send_command (Command .CHIP_ERASE )
186- self ._receive_reply (Command .CHIP_ERASE , num )
196+ param_data = struct .pack ("<II" , 0 , size )
197+ num = self ._send_command (Command .BEGIN_DOWNLOAD_WITH_ERASE , param_data )
198+ self ._receive_reply (Command .BEGIN_DOWNLOAD_WITH_ERASE , num )
187199
188- async def erase_chip (self ) -> None :
200+ async def erase_and_begin_download (self , size ) -> None :
189201 """
190- Erases the external flash memory chip.
202+ Erases the external flash memory chip by the amount required to
203+ flash the new firmware. Also prepares firmware download.
191204
192- This operation takes about 60 seconds.
205+ Args:
206+ size: How much to erase.
193207 """
194208 return await asyncio .get_running_loop ().run_in_executor (
195- None ,
196- self .erase_chip_sync ,
209+ None , self .erase_and_begin_download_sync , size
197210 )
198211
199212 def start_app_sync (self ) -> None :
@@ -241,7 +254,12 @@ def get_version_sync(self) -> Tuple[int, int]:
241254 Blocking version of :meth:`get_version`.
242255 """
243256 num = self ._send_command (Command .GET_VERSION )
244- payload = self ._receive_reply (Command .GET_VERSION , num )
257+ # On certain USB 3.0 systems, the brick reply contains the command
258+ # we just sent written over it. This means we don't get the correct
259+ # header and length info. Since the command here is smaller than the
260+ # reply, the paypload does not get overwritten, so we can still get
261+ # the version info since we know the expected reply size.
262+ payload = self ._receive_reply (Command .GET_VERSION , num , force_length = 13 )
245263 return struct .unpack ("<II" , payload )
246264
247265 async def get_version (self ) -> Tuple [int , int ]:
0 commit comments