1use crate::{
2 api_log, api_types, display,
3 error::{CubeProgrammerError, CubeProgrammerResult},
4 utility,
5};
6use bon::bon;
7use derive_more::Into;
8use log::{debug, error};
9use std::{
10 cell::RefCell,
11 collections::HashMap,
12 sync::{Arc, Mutex},
13};
14use stm32cubeprogrammer_sys::libloading;
15use stm32cubeprogrammer_sys::SRAM_BASE_ADDRESS;
16
17macro_rules! verify_api_struct {
18 ($api:expr, $($field:ident),*) => {{
19 $(
20 match &$api.$field {
21 Ok(_) => (), Err(err) => return Err(CubeProgrammerError::MissingDllSymbol{message: format!(
23 "Missing symbol '{}': {}", stringify!($field), err
24 )}),
25 }
26 )*
27 Ok(())
28 }};
29}
30
31type ProbeRegistry = HashMap<crate::probe::Serial, Option<crate::probe::Probe>>;
35
36pub struct CubeProgrammer {
39 api: stm32cubeprogrammer_sys::CubeProgrammer_API,
41
42 probe_registry: RefCell<ProbeRegistry>,
44}
45
46#[derive(Debug)]
48pub struct ConnectedProgrammer<'a> {
49 programmer: &'a CubeProgrammer,
51 probe: crate::probe::Probe,
53 general_information: api_types::GeneralInformation,
55}
56
57#[derive(Debug)]
59pub struct ConnectedFusProgrammer<'a> {
60 programmer: ConnectedProgrammer<'a>,
61 fus_info: crate::fus::Information,
62}
63
64#[bon]
65impl CubeProgrammer {
66 #[builder]
72 pub fn new(
73 cube_programmer_dir: &impl AsRef<std::path::Path>,
74 log_verbosity: Option<api_log::Verbosity>,
75 display_callback: Option<Arc<Mutex<dyn crate::DisplayCallback>>>,
76 ) -> Result<Self, CubeProgrammerError> {
77 use stm32cubeprogrammer_sys::{PATH_API_LIBRARY_RELATIVE, PATH_LOADER_DIR_RELATIVE};
78
79 let api_path = cube_programmer_dir
80 .as_ref()
81 .join(PATH_API_LIBRARY_RELATIVE)
82 .canonicalize()
83 .map_err(CubeProgrammerError::FileIo)?;
84
85 let loader_path = cube_programmer_dir
86 .as_ref()
87 .join(PATH_LOADER_DIR_RELATIVE)
88 .canonicalize()
89 .map_err(CubeProgrammerError::FileIo)?;
90
91 debug!("API path: {:?}", api_path);
92 debug!("Loader path: {:?}", loader_path);
93
94 let library = Self::load_library(&api_path).map_err(CubeProgrammerError::LibLoading)?;
95
96 let api = unsafe {
97 stm32cubeprogrammer_sys::CubeProgrammer_API::from_library(library)
98 .map_err(CubeProgrammerError::LibLoading)?
99 };
100
101 verify_api_struct!(
103 api,
104 setVerbosityLevel,
105 setDisplayCallbacks,
106 setLoadersPath,
107 getStLinkList,
108 deleteInterfaceList,
109 connectStLink,
110 getDeviceGeneralInf,
111 disconnect,
112 startFus,
113 reset,
114 downloadFile,
115 massErase,
116 saveMemoryToFile,
117 sendOptionBytesCmd,
118 readUnprotect,
119 checkDeviceConnection,
120 readMemory,
121 freeLibraryMemory,
122 startWirelessStack,
123 writeCortexRegistres,
124 readCortexReg,
125 firmwareDelete,
126 firmwareUpgrade
127 )?;
128
129 if let Some(display_callback) = display_callback {
130 debug!("Set display callback handler");
131 display::set_display_callback_handler(display_callback);
132 }
133
134 unsafe {
135 {
136 let verbosity = log_verbosity.unwrap_or({
137 debug!("Use default verbosity level");
138 api_log::Verbosity::Level3
139 });
140
141 debug!("Set verbosity level: {}", verbosity);
142
143 api.setVerbosityLevel(verbosity.into());
144 }
145
146 let display_callbacks = stm32cubeprogrammer_sys::displayCallBacks {
147 initProgressBar: Some(api_log::display_callback_init_progressbar),
148 logMessage: Some(api_log::display_callback_log_message),
149 loadBar: Some(api_log::display_callback_load_bar),
150 };
151
152 api.setDisplayCallbacks(display_callbacks);
153 api.setLoadersPath(utility::path_to_cstring(loader_path)?.as_ptr());
154 }
155
156 Ok(Self {
157 api,
158 probe_registry: RefCell::new(HashMap::new()),
159 })
160 }
161
162 fn scan_for_probes(&self) -> CubeProgrammerResult<()> {
165 let mut debug_parameters =
166 std::ptr::null_mut::<stm32cubeprogrammer_sys::debugConnectParameters>();
167 let return_value = unsafe { self.api.getStLinkList(&mut debug_parameters, 0) };
168
169 if return_value < 0 || debug_parameters.is_null() {
170 return Err(CubeProgrammerError::ActionOutputUnexpected {
171 action: crate::error::Action::ListConnectedProbes,
172 unexpected_output: crate::error::UnexpectedOutput::Null,
173 });
174 }
175
176 let slice = unsafe {
177 std::slice::from_raw_parts(
178 debug_parameters as *mut crate::probe::Probe,
179 return_value as _,
180 )
181 };
182
183 let mut connected_probes = self.probe_registry.borrow_mut();
184
185 connected_probes.retain(|_, value| value.is_none());
187
188 for probe in slice {
189 connected_probes
191 .entry(probe.serial_number().to_string().into())
192 .or_insert_with(|| Some(probe.clone()));
193 }
194
195 unsafe {
197 self.api.deleteInterfaceList();
198 }
199
200 Ok(())
201 }
202
203 pub fn list_available_probes(&self) -> CubeProgrammerResult<Vec<crate::probe::Serial>> {
205 self.scan_for_probes()?;
206
207 let connected_probes = self.probe_registry.borrow();
208
209 Ok(connected_probes
210 .values()
211 .filter_map(|probe| {
212 probe
213 .as_ref()
214 .map(|probe| probe.serial_number().to_string().into())
215 })
216 .collect())
217 }
218
219 fn insert_probe(&self, probe: &crate::probe::Probe) {
221 let mut connected_probes = self.probe_registry.borrow_mut();
222 connected_probes.insert(probe.serial_number().to_owned().into(), Some(probe.clone()));
223 }
224
225 pub fn connect_to_target(
227 &self,
228 probe_serial_number: &crate::probe::Serial,
229 protocol: &crate::probe::Protocol,
230 connection_parameters: &crate::probe::ConnectionParameters,
231 ) -> CubeProgrammerResult<ConnectedProgrammer> {
232 let mut connected_probes = self.probe_registry.borrow_mut();
233
234 if let Some(probe) = connected_probes.get_mut(probe_serial_number) {
235 if let Some(inner) = probe.take() {
236 match api_types::ReturnCode::<0>::from(unsafe {
238 self.api.connectStLink(*crate::probe::Probe::new(
239 &inner,
240 protocol,
241 connection_parameters,
242 ))
243 })
244 .check(crate::error::Action::Connect)
245 {
246 Ok(_) => {
247 let general_information = unsafe { self.api.getDeviceGeneralInf() };
249 if general_information.is_null() {
250 *probe = Some(inner);
252
253 unsafe { self.api.disconnect() };
254
255 return Err(CubeProgrammerError::ActionOutputUnexpected {
256 action: crate::error::Action::ReadTargetInfo,
257 unexpected_output: crate::error::UnexpectedOutput::Null,
258 });
259 }
260
261 let general_information =
263 api_types::GeneralInformation::from(unsafe { *general_information });
264
265 Ok(ConnectedProgrammer {
266 programmer: self,
267 probe: inner,
268 general_information,
269 })
270 }
271 Err(e) => {
272 error!(
273 "Cannot connect to target via probe with serial number: {}",
274 probe_serial_number
275 );
276
277 *probe = Some(inner);
279
280 Err(e)
281 }
282 }
283 } else {
284 Err(CubeProgrammerError::Parameter {
285 action: crate::error::Action::Connect,
286 message: format!(
287 "Probe with serial number {} already in use",
288 probe_serial_number
289 ),
290 })
291 }
292 } else {
293 Err(CubeProgrammerError::Parameter {
294 action: crate::error::Action::Connect,
295 message: format!("Probe with serial number {} not found", probe_serial_number),
296 })
297 }
298 }
299
300 pub fn connect_to_target_fus(
308 &self,
309 probe_serial_number: &crate::probe::Serial,
310 protocol: &crate::probe::Protocol,
311 ) -> CubeProgrammerResult<ConnectedFusProgrammer> {
312 let connected = self.connect_to_target(
314 probe_serial_number,
315 protocol,
316 &crate::probe::ConnectionParameters {
317 frequency: crate::probe::Frequency::Highest,
318 reset_mode: crate::probe::ResetMode::Hardware,
319 connection_mode: crate::probe::ConnectionMode::Normal,
320 },
321 )?;
322
323 connected.check_fus_support()?;
324
325 api_types::ReturnCode::<1>::from(unsafe { connected.api().startFus() })
327 .check(crate::error::Action::StartFus)?;
328
329 connected.disconnect();
331
332 let connected = self.connect_to_target(
334 probe_serial_number,
335 protocol,
336 &crate::probe::ConnectionParameters {
337 frequency: crate::probe::Frequency::Highest,
338 reset_mode: crate::probe::ResetMode::Hardware,
339 connection_mode: crate::probe::ConnectionMode::HotPlug,
340 },
341 )?;
342
343 let fus_info = connected.read_fus_info()?;
345
346 Ok(ConnectedFusProgrammer {
347 programmer: connected,
348 fus_info,
349 })
350 }
351
352 fn load_library(
354 api_library_path: impl AsRef<std::ffi::OsStr>,
355 ) -> Result<libloading::Library, libloading::Error> {
356 #[cfg(windows)]
357 unsafe fn load_inner(
358 path: impl AsRef<std::ffi::OsStr>,
359 ) -> Result<libloading::Library, libloading::Error> {
360 let library: libloading::Library = unsafe {
361 libloading::os::windows::Library::load_with_flags(
362 path,
363 libloading::os::windows::LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
364 | libloading::os::windows::LOAD_LIBRARY_SEARCH_SYSTEM32
365 | libloading::os::windows::LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
366 )?
367 .into()
368 };
369
370 Ok(library)
371 }
372
373 #[cfg(unix)]
374 unsafe fn load_inner(
375 path: impl AsRef<std::ffi::OsStr>,
376 ) -> Result<libloading::Library, libloading::Error> {
377 use stm32cubeprogrammer_sys::libloading;
378
379 let library: libloading::Library =
380 unsafe { libloading::os::unix::Library::new(path)?.into() };
381
382 Ok(library)
383 }
384
385 unsafe { load_inner(api_library_path.as_ref()) }
386 }
387}
388
389impl std::fmt::Debug for CubeProgrammer {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 f.debug_struct("CubeProgrammerApi").finish_non_exhaustive()
392 }
393}
394
395impl Drop for ConnectedProgrammer<'_> {
396 fn drop(&mut self) {
398 unsafe {
399 self.api().disconnect();
400 }
401
402 self.programmer.insert_probe(&self.probe);
403 }
404}
405
406impl ConnectedProgrammer<'_> {
407 pub fn disconnect(self) {
409 }
411
412 pub fn general_information(&self) -> &api_types::GeneralInformation {
414 &self.general_information
415 }
416
417 fn api(&self) -> &stm32cubeprogrammer_sys::CubeProgrammer_API {
418 &self.programmer.api
419 }
420
421 fn check_fus_support(&self) -> CubeProgrammerResult<()> {
422 if !self.general_information.fus_support {
423 return Err(CubeProgrammerError::ActionNotSupported {
424 action: crate::error::Action::StartFus,
425 message: format!(
426 "Connection target {} does not support FUS",
427 self.general_information.name
428 ),
429 });
430 }
431
432 Ok(())
433 }
434
435 fn read_fus_info(&self) -> CubeProgrammerResult<crate::fus::Information> {
437 fn u32_to_version(version: u32) -> crate::fus::Version {
439 const INFO_VERSION_MAJOR_OFFSET: u32 = 24;
440 const INFO_VERSION_MAJOR_MASK: u32 = 0xff000000;
441 const INFO_VERSION_MINOR_OFFSET: u32 = 16;
442 const INFO_VERSION_MINOR_MASK: u32 = 0x00ff0000;
443 const INFO_VERSION_SUB_OFFSET: u32 = 8;
444 const INFO_VERSION_SUB_MASK: u32 = 0x0000ff00;
445 const INFO_VERSION_TYPE_OFFSET: u32 = 0;
446 const INFO_VERSION_TYPE_MASK: u32 = 0x00000000f;
447
448 crate::fus::Version {
449 major: ((version & INFO_VERSION_MAJOR_MASK) >> INFO_VERSION_MAJOR_OFFSET) as u8,
450 minor: ((version & INFO_VERSION_MINOR_MASK) >> INFO_VERSION_MINOR_OFFSET) as u8,
451 sub: ((version & INFO_VERSION_SUB_MASK) >> INFO_VERSION_SUB_OFFSET) as u8,
452 r#type: Some(
453 ((version & INFO_VERSION_TYPE_MASK) >> INFO_VERSION_TYPE_OFFSET) as u8,
454 ),
455 }
456 }
457
458 const DEVICE_INFO_TABLE_STATE_OFFSET: u32 = 0;
460 const FUS_VERSION_OFFSET: u32 = 12;
466 const WIRELESS_STACK_VERSION_OFFSET: u32 = 20;
468 const UID64_OFFSET: u32 = 40;
473 const DEVICE_ID_OFFSET: u32 = 48;
474
475 const FUS_DEVICE_INFO_TABLE_VALIDITY_KEYWORD: u32 = 0xA94656B9;
477 const SRAM2A_BASE_ADDRESS: u32 = SRAM_BASE_ADDRESS + 0x00030000;
479
480 let info_table_address = self.read_memory::<u32>(SRAM2A_BASE_ADDRESS, 1)?[0];
481
482 if info_table_address == 0 {
483 return Err(CubeProgrammerError::ActionOutputUnexpected {
484 action: crate::error::Action::ReadFusInfo,
485 unexpected_output: crate::error::UnexpectedOutput::Null,
486 });
487 }
488
489 let device_info_table_state =
490 self.read_memory::<u32>(info_table_address + DEVICE_INFO_TABLE_STATE_OFFSET, 1)?[0];
491
492 let fus_version = self.read_memory::<u32>(info_table_address + FUS_VERSION_OFFSET, 1)?[0];
493
494 let wireless_stack_version =
495 self.read_memory::<u32>(info_table_address + WIRELESS_STACK_VERSION_OFFSET, 1)?[0];
496
497 let uid64 = self.read_memory::<u64>(info_table_address + UID64_OFFSET, 1)?[0];
498
499 let device_id = self.read_memory::<u16>(info_table_address + DEVICE_ID_OFFSET, 1)?[0];
500
501 if device_info_table_state != FUS_DEVICE_INFO_TABLE_VALIDITY_KEYWORD {
502 error!("Read FUS info table is not valid. Return default FUS info");
503 return Err(CubeProgrammerError::ActionOutputUnexpected {
504 action: crate::error::Action::ReadFusInfo,
505 unexpected_output: crate::error::UnexpectedOutput::Null,
506 });
507 }
508
509 Ok(crate::fus::Information {
510 fus_version: u32_to_version(fus_version),
511 wireless_stack_version: u32_to_version(wireless_stack_version),
512 device_id,
513 uid64,
514 })
515 }
516
517 pub fn reset_target(&self, reset_mode: crate::probe::ResetMode) -> CubeProgrammerResult<()> {
519 self.check_connection()?;
520 api_types::ReturnCode::<0>::from(unsafe { self.api().reset(reset_mode.into()) })
521 .check(crate::error::Action::Reset)
522 }
523
524 pub fn download_hex_file(
526 &self,
527 file_path: impl AsRef<std::path::Path>,
528 skip_erase: bool,
529 verify: bool,
530 ) -> CubeProgrammerResult<()> {
531 #[cfg(feature = "ihex")]
533 {
534 let file_content = std::fs::read(&file_path).map_err(CubeProgrammerError::FileIo)?;
537 let file_content =
538 std::str::from_utf8(&file_content).map_err(|_| CubeProgrammerError::Parameter {
539 action: crate::error::Action::DownloadFile,
540 message: "Invalid intelhex file".to_string(),
541 })?;
542
543 let reader = ihex::Reader::new_with_options(
544 file_content,
545 ihex::ReaderOptions {
546 stop_after_first_error: true,
547 stop_after_eof: true,
548 },
549 );
550
551 for record in reader {
552 match record {
553 Ok(_) => {}
554 Err(e) => {
555 return Err(CubeProgrammerError::Parameter {
556 action: crate::error::Action::DownloadFile,
557 message: format!("Invalid intelhex file: {}", e),
558 });
559 }
560 }
561 }
562 }
563
564 self.check_connection()?;
565
566 let file_path = utility::path_to_widestring(file_path);
567
568 api_types::ReturnCode::<0>::from(unsafe {
569 self.api().downloadFile(
570 file_path?.as_ptr(),
571 0,
572 if skip_erase { 1 } else { 0 },
573 if verify { 1 } else { 0 },
574 std::ptr::null(),
575 )
576 })
577 .check(crate::error::Action::DownloadFile)
578 }
579
580 pub fn download_bin_file(
582 &self,
583 file_path: impl AsRef<std::path::Path>,
584 start_address: u32,
585 skip_erase: bool,
586 verify: bool,
587 ) -> CubeProgrammerResult<()> {
588 self.check_connection()?;
589
590 let file_path = utility::path_to_widestring(file_path);
591
592 api_types::ReturnCode::<0>::from(unsafe {
593 self.api().downloadFile(
594 file_path?.as_ptr(),
595 start_address,
596 if skip_erase { 1 } else { 0 },
597 if verify { 1 } else { 0 },
598 std::ptr::null(),
599 )
600 })
601 .check(crate::error::Action::DownloadFile)
602 }
603
604 pub fn mass_erase(&self) -> CubeProgrammerResult<()> {
606 self.check_connection()?;
607
608 api_types::ReturnCode::<0>::from(unsafe { self.api().massErase(std::ptr::null_mut()) })
609 .check(crate::error::Action::MassErase)
610 }
611
612 pub fn save_memory(
615 &self,
616 file_path: impl AsRef<std::path::Path>,
617 start_address: u32,
618 size_bytes: u32,
619 ) -> CubeProgrammerResult<()> {
620 self.check_connection()?;
621
622 api_types::ReturnCode::<0>::from(unsafe {
623 self.api().saveMemoryToFile(
624 i32::try_from(start_address).map_err(|x| CubeProgrammerError::Parameter {
625 action: crate::error::Action::SaveMemory,
626 message: format!("Start address exceeds max value: {}", x),
627 })?,
628 i32::try_from(size_bytes).map_err(|x| CubeProgrammerError::Parameter {
629 action: crate::error::Action::SaveMemory,
630 message: format!("Size exceeds max value: {}", x),
631 })?,
632 utility::path_to_widestring(file_path)?.as_ptr(),
633 )
634 })
635 .check(crate::error::Action::SaveMemory)
636 }
637
638 pub fn enable_read_out_protection(&self) -> CubeProgrammerResult<()> {
640 const COMMAND_ENABLE_ROP_LEVEL_1: &str = "-ob rdp=0xbb";
642
643 self.check_connection()?;
644
645 api_types::ReturnCode::<0>::from(unsafe {
646 self.api().sendOptionBytesCmd(
647 utility::string_to_cstring(COMMAND_ENABLE_ROP_LEVEL_1)?.as_ptr()
648 as *mut std::ffi::c_char,
649 )
650 })
651 .check(crate::error::Action::EnableReadOutProtection)
652 }
653
654 pub fn disable_read_out_protection(&self) -> CubeProgrammerResult<()> {
657 self.check_connection()?;
658 api_types::ReturnCode::<0>::from(unsafe { self.api().readUnprotect() })
659 .check(crate::error::Action::DisableReadOutProtection)?;
660 Ok(())
661 }
662
663 fn check_connection(&self) -> CubeProgrammerResult<()> {
667 api_types::ReturnCode::<1>::from(unsafe { self.api().checkDeviceConnection() })
668 .check(crate::error::Action::CheckConnection)
669 }
670
671 pub fn read_memory<T: bytemuck::Pod + bytemuck::Zeroable>(
682 &self,
683 address: u32,
684 count: usize,
685 ) -> CubeProgrammerResult<Vec<T>> {
686 let size = u32::try_from(std::mem::size_of::<T>() * count).map_err(|x| {
687 CubeProgrammerError::Parameter {
688 action: crate::error::Action::ReadMemory,
689 message: format!("Size exceeds max value: {}", x),
690 }
691 })?;
692
693 let mut data = std::ptr::null_mut();
694
695 api_types::ReturnCode::<0>::from(unsafe {
696 self.api().readMemory(address, &mut data, size)
697 })
698 .check(crate::error::Action::ReadMemory)?;
699
700 if data.is_null() {
701 return Err(CubeProgrammerError::ActionOutputUnexpected {
702 action: crate::error::Action::ReadMemory,
703 unexpected_output: crate::error::UnexpectedOutput::Null,
704 });
705 }
706
707 let pod_data: &[T] =
708 bytemuck::try_cast_slice(unsafe { std::slice::from_raw_parts(data, size as _) })
709 .map_err(|_| CubeProgrammerError::ActionOutputUnexpected {
710 action: crate::error::Action::ReadMemory,
711 unexpected_output: crate::error::UnexpectedOutput::SliceConversion,
712 })?;
713
714 let pod_data = pod_data.to_vec();
715
716 unsafe {
717 self.api().freeLibraryMemory(data as *mut std::ffi::c_void);
718 }
719
720 if pod_data.len() != count {
721 return Err(CubeProgrammerError::ActionOutputUnexpected {
722 action: crate::error::Action::ReadMemory,
723 unexpected_output: crate::error::UnexpectedOutput::SliceLength,
724 });
725 }
726
727 Ok(pod_data)
728 }
729
730 pub fn write_memory<T: bytemuck::Pod + std::fmt::Debug>(
741 &self,
742 address: u32,
743 data: &[T],
744 ) -> CubeProgrammerResult<()> {
745 let size = u32::try_from(std::mem::size_of_val(data)).map_err(|x| {
746 CubeProgrammerError::Parameter {
747 action: crate::error::Action::WriteMemory,
748 message: format!("Size exceeds max value: {}", x),
749 }
750 })?;
751
752 let mut bytes = data
753 .iter()
754 .flat_map(|x| bytemuck::bytes_of(x).to_vec())
755 .collect::<Vec<_>>();
756
757 api_types::ReturnCode::<0>::from(unsafe {
758 self.api()
759 .writeMemory(address, bytes.as_mut_ptr() as *mut i8, size)
760 })
761 .check(crate::error::Action::WriteMemory)
762 }
763
764 pub fn start_wireless_stack(&self) -> CubeProgrammerResult<()> {
766 self.check_fus_support()?;
767
768 api_types::ReturnCode::<1>::from(unsafe { self.api().startWirelessStack() })
769 .check(crate::error::Action::StartWirelessStack)
770 }
771
772 pub fn write_core_register(
774 &self,
775 register: crate::api_types::CoreRegister,
776 value: u32,
777 ) -> CubeProgrammerResult<()> {
778 self.check_connection()?;
779
780 api_types::ReturnCode::<0>::from(unsafe {
781 self.api().writeCortexRegistres(register.into(), value)
782 })
783 .check(crate::error::Action::WriteCoreRegister)
784 }
785
786 pub fn read_core_register(
788 &self,
789 register: crate::api_types::CoreRegister,
790 ) -> CubeProgrammerResult<u32> {
791 self.check_connection()?;
792
793 let mut value = 0;
794
795 api_types::ReturnCode::<0>::from(unsafe {
796 self.api().readCortexReg(register.into(), &mut value)
797 })
798 .check(crate::error::Action::ReadCoreRegister)?;
799
800 Ok(value)
801 }
802}
803
804impl ConnectedFusProgrammer<'_> {
805 pub fn fus_info(&self) -> &crate::fus::Information {
806 &self.fus_info
807 }
808
809 pub fn delete_wireless_stack(&self) -> CubeProgrammerResult<()> {
810 api_types::ReturnCode::<1>::from(unsafe { self.programmer.api().firmwareDelete() })
811 .check(crate::error::Action::DeleteWirelessStack)
812 }
813
814 pub fn upgrade_wireless_stack(
815 &self,
816 file_path: impl AsRef<std::path::Path>,
817 start_address: u32,
818 first_install: bool,
819 verify: bool,
820 start_stack_after_update: bool,
821 ) -> CubeProgrammerResult<()> {
822 self.programmer.check_connection()?;
823
824 api_types::ReturnCode::<1>::from(unsafe {
825 self.programmer.api().firmwareUpgrade(
826 utility::path_to_widestring(file_path)?.as_ptr(),
827 start_address,
828 if first_install { 1 } else { 0 },
829 if verify { 1 } else { 0 },
830 if start_stack_after_update { 1 } else { 0 },
831 )
832 })
833 .check(crate::error::Action::UpgradeWirelessStack)
834 }
835
836 pub fn start_wireless_stack(&self) -> CubeProgrammerResult<()> {
837 self.programmer.start_wireless_stack()
838 }
839
840 pub fn disconnect(self) {
841 self.programmer.disconnect()
842 }
843}