@@ -440,19 +440,23 @@ def _streaming_upload_worker(self, can_id, port, baud, firmware_data, firmware_s
440440 }
441441 print (" Sending STREAM_START command..." , start_cmd )
442442 response = self ._send_json (ser , start_cmd )
443- # {"task": "/can_ota_stream", "canid": 11, "action": "start", "firmware_size": 876784, "page_size": 4096, "chunk_size": 512, "md5": "43ba96b4d18c010201762b840476bf83", "qid": 1}
444- if response and str (response ).find ("success" )<= 0 :
445- print (" ERROR: STREAM_START command failed!" )
446- print (f" Response: { response } " )
447- return False
448- print (" ✓ Streaming session started" )
449- '''b\' [326584][I][CanOtaStreaming.cpp:427] actFromJsonStreaming(): Target CAN ID: 11\\ r\\ n[326586][I][CanOtaStreaming.cpp:456] actFromJsonStreaming(): Stream OTA START to CAN ID 11: size=876784, page_size=4096, chunk_size=512\\ r\\ n[326588][I][CanOtaStreaming.cpp:464] actFromJsonStreaming(): Relaying STREAM_START to slave 0x0B\\ r\\ n[326590][I][CanOtaStreaming.cpp:653] startStreamToSlave(): Starting stream to slave 0x0B: 876784 bytes, 215 pages\\ r\\ n[326968][I][CanOtaStreaming.cpp:622] handleSlaveStreamResponse(): Slave STREAM_ACK: page=65535, bytes=0, nextSeq=0\\ r\\ n++\\ n{"success":true,"qid":1}\\ n--\\ n\\ x00\' '''
450- #self._drain_serial(ser)
451- #self._send_json(ser, {"task": "/can_act", "debug": 1})
452-
443+
444+ # Wait for JSON response
445+ if response and str (response ).find ("success" ) <= 0 :
446+ if status_callback :
447+ status_callback ("STREAM_START command failed" , False )
448+ status_callback (f"Response: { response } " , False )
449+ return
450+
453451 if status_callback :
454452 status_callback ("Streaming session started, uploading..." , True )
455453
454+ # CRITICAL: Wait for slave to initialize via CAN
455+ time .sleep (1.0 )
456+
457+ # Drain any pending data (logs, old ACKs, etc.)
458+ self ._drain_serial (ser , "post-start" , verbose = False )
459+
456460 # Step 4: Stream pages
457461 start_time = time .time ()
458462 seq = 0
@@ -468,14 +472,24 @@ def _streaming_upload_worker(self, can_id, port, baud, firmware_data, firmware_s
468472 if len (page_data ) < PAGE_SIZE :
469473 page_data = page_data + bytes (PAGE_SIZE - len (page_data ))
470474
475+ # Progress display
476+ progress = (page_idx + 1 ) / num_pages * 100
477+ elapsed = time .time () - start_time
478+ speed = bytes_sent / elapsed / 1024 if elapsed > 0 else 0
479+ print (f"\r Page { page_idx + 1 } /{ num_pages } ({ progress :.1f} %) - { speed :.1f} KB/s " , end = "" )
480+
481+
471482 # Send page with retry
472- success , seq = self ._send_page_with_retry (ser = ser , page_idx = page_idx , page_data = page_data , seq_start = seq )
483+ # Send page with retry logic
484+ success , seq , acked_page , acked_bytes = self ._send_page_with_retry (
485+ ser , page_idx , page_data , seq
486+ )
473487 bytes_sent += PAGE_SIZE
474488
475489 if not success :
476490 if status_callback :
477491 status_callback (f"Page { page_idx } failed after retries" , False )
478- return False
492+ return
479493
480494 # Progress callback
481495 if progress_callback :
@@ -560,7 +574,7 @@ def _drain_serial(self, ser, label="", verbose=True):
560574 print (f" [{ label } ] Drained { len (data )} bytes" )
561575 return data
562576
563- def _send_json (self , ser , data , wait_response = True , timeout = 2 .0 ):
577+ def _send_json (self , ser , data , wait_response = True , timeout = 3 .0 ):
564578 """Send JSON command over serial."""
565579 json_str = json .dumps (data , separators = (',' , ':' ))
566580 tx_data = (json_str + '\n ' ).encode ()
@@ -585,19 +599,22 @@ def _send_json(self, ser, data, wait_response=True, timeout=2.0):
585599 return None
586600
587601 def _build_stream_data_packet (self , page_idx , offset , seq , chunk_data ):
588- """Build a binary stream data packet."""
589- header = struct .pack ('<HHHH' , page_idx , offset , len (chunk_data ), seq )
590- packet_body = bytes ([STREAM_DATA ]) + header + chunk_data
591- checksum = sum (packet_body ) & 0xFF
592- return bytes ([SYNC_1 , SYNC_2 ]) + packet_body + bytes ([checksum ])
593-
602+ # Match can_ota_streaming.py wire format + checksum
603+ packet_body = bytes ([SYNC_1 , SYNC_2 , STREAM_DATA ]) + struct .pack (
604+ '>HHHH' , page_idx , offset , len (chunk_data ), seq
605+ ) + chunk_data
606+ checksum = 0
607+ for b in packet_body :
608+ checksum ^= b
609+ return packet_body + bytes ([checksum ])
610+
594611 def _build_stream_finish_packet (self , md5_bytes ):
595- """Build STREAM_FINISH packet with MD5 hash."""
596- header = bytes ([ STREAM_FINISH ])
597- packet_body = header + md5_bytes
598- checksum = sum ( packet_body ) & 0xFF
599- return bytes ([ SYNC_1 , SYNC_2 ]) + packet_body + bytes ([checksum ])
600-
612+ packet_body = bytes ([ SYNC_1 , SYNC_2 , STREAM_FINISH ]) + md5_bytes
613+ checksum = 0
614+ for b in packet_body :
615+ checksum ^= b
616+ return packet_body + bytes ([checksum ])
617+
601618 def _wait_for_session_start (self , ser , timeout = 10.0 ):
602619 """Wait for streaming session start ACK."""
603620 start = time .time ()
@@ -712,13 +729,73 @@ def _send_page_with_retry(self, ser, page_idx, page_data, seq_start,
712729 ser .flush ()
713730
714731 # Wait for ACK
715- success , acked_page , acked_bytes , raw = self ._wait_for_page_ack (ser , page_idx )
732+ success , acked_page , acked_bytes , raw = self .wait_for_stream_ack (ser , page_idx )
716733
717734 if success :
718- return (True , seq )
735+ return (True , seq , acked_page , acked_bytes )
719736
720737 if retry < max_retries - 1 :
721- time .sleep (0.5 )
722- self ._drain_serial (ser )
738+ print (f" [RETRY { retry + 1 } /{ max_retries } ]" , end = "" )
739+ time .sleep (0.5 ) # Small delay before retry
740+ self ._drain_serial (ser , "retry" , verbose = False )
741+
742+ return (False , seq , - 1 , 0 )
743+
744+
745+
746+ def wait_for_stream_ack (self , ser , expected_page : int , timeout : float = PAGE_ACK_TIMEOUT ):
747+ """
748+ Wait for STREAM_ACK response.
749+ Returns: (success, last_complete_page, bytes_received, raw_response)
750+ """
751+ start = time .time ()
752+ buffer = bytearray ()
753+
754+ while time .time () - start < timeout :
755+ if ser .in_waiting :
756+ new_data = ser .read (ser .in_waiting )
757+ buffer .extend (new_data )
758+
759+ # Check for log messages indicating success (for final ACK)
760+ try :
761+ text = bytes (buffer ).decode ('utf-8' , errors = 'replace' )
762+ print ("DEBUG TEXT:" , text )
763+ if "OTA COMPLETE" in text or "Rebooting" in text :
764+ return (True , expected_page , 0 , bytes (buffer ))
765+ except :
766+ pass
767+
768+ # Search for STREAM_ACK response
769+ # Format: [SYNC][CMD][status][canId][lastPage_L][lastPage_H][bytes(4)][nextSeq(2)][reserved(2)]
770+ i = 0
771+ while i < len (buffer ) - 15 :
772+ if buffer [i ] == SYNC_1 and buffer [i + 1 ] == SYNC_2 :
773+ cmd = buffer [i + 2 ]
774+
775+ if cmd == STREAM_ACK :
776+ status = buffer [i + 3 ]
777+ can_id = buffer [i + 4 ]
778+ last_page = buffer [i + 5 ] | (buffer [i + 6 ] << 8 ) # Little endian
779+ bytes_recv = struct .unpack ('<I' , bytes (buffer [i + 7 :i + 11 ]))[0 ]
780+ next_seq = buffer [i + 11 ] | (buffer [i + 12 ] << 8 )
781+
782+ if status == 0 : # CAN_OTA_OK
783+ return (True , last_page , bytes_recv , bytes (buffer ))
784+ else :
785+ print (f" STREAM_ACK with error status: { status } " )
786+ return (False , last_page , bytes_recv , bytes (buffer ))
787+
788+ elif cmd == STREAM_NAK :
789+ status = buffer [i + 3 ]
790+ can_id = buffer [i + 4 ]
791+ error_page = buffer [i + 5 ] | (buffer [i + 6 ] << 8 )
792+ missing_offset = buffer [i + 7 ] | (buffer [i + 8 ] << 8 )
793+
794+ print (f" STREAM_NAK: status={ status } , page={ error_page } , offset={ missing_offset } " )
795+ return (False , error_page , missing_offset , bytes (buffer ))
796+
797+ i += 1
798+ else :
799+ time .sleep (0.001 )
723800
724- return (False , seq )
801+ return (False , - 1 , 0 , bytes ( buffer ) )
0 commit comments