Skip to main content

esp_radio/wifi/
csi.rs

1//! Wi-Fi Channel State Information (CSI).
2
3use alloc::boxed::Box;
4use core::marker::PhantomData;
5
6use esp_hal::time::{Duration, Instant};
7
8use super::{WifiError, c_types::c_void};
9#[cfg(any(wifi_mac_version = "2", wifi_mac_version = "3"))]
10use crate::sys::include::wifi_csi_acquire_config_t;
11use crate::{
12    sys::include::{
13        esp_wifi_set_csi,
14        esp_wifi_set_csi_config,
15        esp_wifi_set_csi_rx_cb,
16        wifi_csi_config_t,
17        wifi_csi_info_t,
18    },
19    wifi::{SecondaryChannel, esp_wifi_result},
20};
21
22/// CSI (Channel State Information) packet metadata and associated packet details.
23///
24/// This structure contains the raw CSI data, along with necessary metadata
25/// from the received Wi-Fi packet (MAC addresses, sequence number, packet headers).
26#[repr(transparent)]
27#[derive(Debug)]
28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29#[instability::unstable]
30pub struct WifiCsiInfo<'a> {
31    inner: *const wifi_csi_info_t,
32    _lt: PhantomData<&'a ()>,
33}
34
35impl<'a> WifiCsiInfo<'_> {
36    /// Received Signal Strength Indicator (RSSI) of the packet.
37    #[instability::unstable]
38    pub fn rssi(&self) -> i8 {
39        // Signed bitfields are broken in rust-bingen, see https://github.com/esp-rs/esp-wifi-sys/issues/482
40        // Hard-casting it from C-signed to i8 gives correct values, so no need for workaround
41        // here.
42        unsafe { (*self.inner).rx_ctrl.rssi() as i8 }
43    }
44
45    /// Data rate of the packet.
46    #[instability::unstable]
47    pub fn rate(&self) -> u8 {
48        unsafe { (*self.inner).rx_ctrl.rate() as u8 }
49    }
50
51    /// Protocol of the received packet, 0: non HT(11bg) packet; 1: HT(11n) packet; 3: VHT(11ac)
52    /// packet.
53    #[instability::unstable]
54    #[cfg(wifi_mac_version = "1")]
55    pub fn packet_mode(&self) -> u8 {
56        unsafe { (*self.inner).rx_ctrl.sig_mode() as u8 }
57    }
58
59    /// Modulation Coding Scheme. If is HT(11n) packet, shows the modulation, range from 0 to
60    /// 76(MSC0 ~ MCS76).
61    #[instability::unstable]
62    #[cfg(wifi_mac_version = "1")]
63    pub fn modulation_coding_scheme(&self) -> u8 {
64        unsafe { (*self.inner).rx_ctrl.mcs() as u8 }
65    }
66
67    /// Channel Bandwidth of the packet. 0: 20MHz; 1: 40MHz.
68    #[instability::unstable]
69    #[cfg(wifi_mac_version = "1")]
70    pub fn cwb(&self) -> bool {
71        unsafe { (*self.inner).rx_ctrl.cwb() != 0 }
72    }
73
74    /// Set to 1 indicates that channel estimate smoothing is recommended.
75    /// Set to 0 indicates that only per-carrier independent (unsmoothed) channel estimate is
76    /// recommended.
77    #[instability::unstable]
78    #[cfg(wifi_mac_version = "1")]
79    pub fn smoothing(&self) -> bool {
80        unsafe { (*self.inner).rx_ctrl.smoothing() != 0 }
81    }
82
83    /// Set to 1 indicates that the PPDU is not a sounding PPDU.
84    /// Set to 0 indicates that PPDU is a sounding PPDU.
85    #[instability::unstable]
86    #[cfg(wifi_mac_version = "1")]
87    pub fn not_sounding(&self) -> bool {
88        unsafe { (*self.inner).rx_ctrl.not_sounding() != 0 }
89    }
90
91    /// Aggregation. 0: MPDU packet; 1: AMPDU packet.
92    #[instability::unstable]
93    #[cfg(wifi_mac_version = "1")]
94    pub fn aggregation(&self) -> bool {
95        unsafe { (*self.inner).rx_ctrl.aggregation() != 0 }
96    }
97
98    /// Space Time Block Code(STBC). 0: non STBC packet; 1: STBC packet.
99    #[instability::unstable]
100    #[cfg(wifi_mac_version = "1")]
101    pub fn space_time_block_code(&self) -> u8 {
102        unsafe { (*self.inner).rx_ctrl.stbc() as u8 }
103    }
104
105    /// Forward Error Correction(FEC). Flag is set for 11n packets which are LDPC.
106    #[instability::unstable]
107    #[cfg(wifi_mac_version = "1")]
108    pub fn forward_error_correction_coding(&self) -> bool {
109        unsafe { (*self.inner).rx_ctrl.fec_coding() != 0 }
110    }
111
112    /// Short Guide Interval(SGI). 0: Long GI; 1: Short GI.
113    #[instability::unstable]
114    #[cfg(wifi_mac_version = "1")]
115    pub fn short_guide_interval(&self) -> bool {
116        unsafe { (*self.inner).rx_ctrl.sgi() != 0 }
117    }
118
119    /// Noise floor in dBm of Radio Frequency Module(RF).
120    #[instability::unstable]
121    pub fn noise_floor(&self) -> i8 {
122        unsafe { (*self.inner).rx_ctrl.noise_floor() as i8 }
123    }
124
125    /// The number of subframes aggregated in AMPDU.
126    #[instability::unstable]
127    #[cfg(wifi_mac_version = "1")]
128    pub fn ampdu_count(&self) -> u8 {
129        unsafe { (*self.inner).rx_ctrl.ampdu_cnt() as u8 }
130    }
131
132    /// Primary channel on which this packet is received.
133    #[instability::unstable]
134    pub fn channel(&self) -> u8 {
135        unsafe { (*self.inner).rx_ctrl.channel() as u8 }
136    }
137
138    /// [`SecondaryChannel`] on which this packet is received.
139    #[instability::unstable]
140    pub fn secondary_channel(&self) -> SecondaryChannel {
141        cfg_select! {
142            wifi_mac_version = "1" => {
143                SecondaryChannel::from_raw(unsafe { (*self.inner).rx_ctrl.secondary_channel() })
144            }
145            _ => SecondaryChannel::from_raw(unsafe { (*self.inner).rx_ctrl.second() }),
146        }
147    }
148
149    /// The local time in microseconds when this packet is received. It is precise only if modem
150    /// sleep or light sleep is not enabled.
151    #[instability::unstable]
152    pub fn timestamp(&self) -> Instant {
153        let raw_ticks = unsafe { (*self.inner).rx_ctrl.timestamp() as u64 };
154
155        Instant::EPOCH + Duration::from_micros(raw_ticks)
156    }
157
158    /// Antenna number from which this packet is received.
159    /// 0: Wi-Fi antenna 0; 1: Wi-Fi antenna 1.
160    #[instability::unstable]
161    #[cfg(wifi_mac_version = "1")]
162    pub fn antenna(&self) -> u8 {
163        unsafe { (*self.inner).rx_ctrl.ant() as u8 }
164    }
165
166    /// The length of the reception MPDU.
167    #[instability::unstable]
168    pub fn signal_length(&self) -> u16 {
169        unsafe { (*self.inner).rx_ctrl.sig_len() as u16 }
170    }
171
172    /// State of the packet.
173    /// 0: no error; others: failure.
174    #[instability::unstable]
175    pub fn rx_state(&self) -> u8 {
176        unsafe { (*self.inner).rx_ctrl.rx_state() as u8 }
177    }
178
179    /// Indicate whether the reception frame is from interface 0.
180    #[instability::unstable]
181    #[cfg(esp32c6)]
182    pub fn rx_match0(&self) -> bool {
183        unsafe { (*self.inner).rx_ctrl.rxmatch0() != 0 }
184    }
185
186    /// Indicate whether the reception frame is from interface 1.
187    #[instability::unstable]
188    #[cfg(not(wifi_mac_version = "1"))]
189    pub fn rx_match1(&self) -> bool {
190        unsafe { (*self.inner).rx_ctrl.rxmatch1() != 0 }
191    }
192
193    /// Indicate whether the reception frame is from interface 2.
194    #[instability::unstable]
195    #[cfg(not(wifi_mac_version = "1"))]
196    pub fn rx_match2(&self) -> bool {
197        unsafe { (*self.inner).rx_ctrl.rxmatch2() != 0 }
198    }
199
200    /// Indicate whether the reception frame is from interface 3.
201    #[instability::unstable]
202    #[cfg(not(wifi_mac_version = "1"))]
203    pub fn rx_match3(&self) -> bool {
204        unsafe { (*self.inner).rx_ctrl.rxmatch3() != 0 }
205    }
206
207    /// HE-SIGA1 or HT-SIG.
208    #[instability::unstable]
209    #[cfg(not(wifi_mac_version = "1"))]
210    pub fn he_siga1(&self) -> u32 {
211        unsafe { (*self.inner).rx_ctrl.he_siga1 }
212    }
213
214    /// Reception state, 0: successful, others: failure.
215    #[instability::unstable]
216    #[cfg(not(wifi_mac_version = "1"))]
217    pub fn rx_end_state(&self) -> u8 {
218        unsafe { (*self.inner).rx_ctrl.rxend_state() as u8 }
219    }
220
221    /// HE-SIGA2.
222    #[instability::unstable]
223    #[cfg(not(wifi_mac_version = "1"))]
224    pub fn he_siga2(&self) -> u16 {
225        unsafe { (*self.inner).rx_ctrl.he_siga2 }
226    }
227
228    /// Indicate whether the reception is a group addressed frame.
229    #[instability::unstable]
230    #[cfg(not(wifi_mac_version = "1"))]
231    pub fn is_group(&self) -> bool {
232        unsafe { (*self.inner).rx_ctrl.is_group() != 0 }
233    }
234
235    /// The length of the channel information.
236    #[instability::unstable]
237    #[cfg(not(wifi_mac_version = "1"))]
238    pub fn rx_channel_estimate_length(&self) -> u32 {
239        unsafe { (*self.inner).rx_ctrl.rx_channel_estimate_len() }
240    }
241
242    /// Indicate the channel information is valid.
243    #[instability::unstable]
244    #[cfg(not(wifi_mac_version = "1"))]
245    pub fn rx_channel_estimate_info_valid(&self) -> bool {
246        unsafe { (*self.inner).rx_ctrl.rx_channel_estimate_info_vld() != 0 }
247    }
248
249    /// The format of the reception frame.
250    #[instability::unstable]
251    #[cfg(not(wifi_mac_version = "1"))]
252    pub fn cur_bb_format(&self) -> u8 {
253        unsafe { (*self.inner).rx_ctrl.cur_bb_format() as u8 }
254    }
255
256    /// Indicate whether the reception MPDU is a S-MPDU.
257    #[instability::unstable]
258    #[cfg(wifi_mac_version = "2")]
259    pub fn cur_single_mpdu(&self) -> bool {
260        unsafe { (*self.inner).rx_ctrl.cur_single_mpdu() != 0 }
261    }
262
263    /// The length of HE-SIGB.
264    #[instability::unstable]
265    #[cfg(wifi_mac_version = "2")]
266    pub fn he_sigb_length(&self) -> u8 {
267        unsafe { (*self.inner).rx_ctrl.he_sigb_len() as u8 }
268    }
269
270    /// The length of the reception MPDU excluding the FCS.
271    #[instability::unstable]
272    #[cfg(not(wifi_mac_version = "1"))]
273    pub fn dump_length(&self) -> u32 {
274        unsafe { (*self.inner).rx_ctrl.dump_len() }
275    }
276
277    /// Source MAC address of the CSI data.
278    #[instability::unstable]
279    pub fn mac(&self) -> &[u8; 6] {
280        unsafe { &(*self.inner).mac }
281    }
282
283    /// Destination MAC address of the CSI data.
284    #[instability::unstable]
285    pub fn destination_mac(&self) -> &[u8; 6] {
286        unsafe { &(*self.inner).dmac }
287    }
288
289    /// First four bytes of the CSI data is invalid or not, true indicates the first four bytes is
290    /// invalid due to hardware limitation.
291    #[instability::unstable]
292    pub fn first_word_invalid(&self) -> bool {
293        unsafe { (*self.inner).first_word_invalid }
294    }
295
296    /// Valid buffer of CSI data.
297    #[instability::unstable]
298    pub fn buf(&self) -> &[i8] {
299        unsafe {
300            if (*self.inner).buf.is_null() || (*self.inner).len == 0 {
301                &[]
302            } else {
303                core::slice::from_raw_parts((*self.inner).buf, (*self.inner).len as usize)
304            }
305        }
306    }
307
308    /// Header of the wifi packet.
309    #[instability::unstable]
310    pub fn header(&self) -> &[u8] {
311        unsafe {
312            if (*self.inner).hdr.is_null() {
313                &[]
314            } else {
315                const HDR_LEN: usize = 24;
316                core::slice::from_raw_parts((*self.inner).hdr, HDR_LEN)
317            }
318        }
319    }
320
321    /// Payload of the wifi packet.
322    #[instability::unstable]
323    pub fn payload(&self) -> &[u8] {
324        unsafe {
325            if (*self.inner).payload.is_null() || (*self.inner).payload_len == 0 {
326                &[]
327            } else {
328                core::slice::from_raw_parts(
329                    (*self.inner).payload,
330                    (*self.inner).payload_len as usize,
331                )
332            }
333        }
334    }
335
336    /// Rx sequence number of the wifi packet.
337    #[instability::unstable]
338    pub fn rx_sequence(&self) -> u16 {
339        unsafe { (*self.inner).rx_seq }
340    }
341}
342
343pub(crate) trait CsiCallback: FnMut(WifiCsiInfo<'_>) {}
344
345impl<T> CsiCallback for T where T: FnMut(WifiCsiInfo<'_>) {}
346
347unsafe extern "C" fn csi_rx_cb<C: CsiCallback>(ctx: *mut c_void, data: *mut wifi_csi_info_t) {
348    unsafe {
349        let csi_callback = &mut *(ctx as *mut C);
350
351        let data = WifiCsiInfo {
352            inner: data,
353            _lt: PhantomData,
354        };
355        csi_callback(data);
356    }
357}
358
359/// Channel state information (CSI) configuration.
360#[derive(Debug, Clone, PartialEq, Eq)]
361#[cfg_attr(feature = "defmt", derive(defmt::Format))]
362#[cfg(wifi_mac_version = "1")]
363#[instability::unstable]
364pub struct CsiConfig {
365    /// Enable to receive legacy long training field(lltf) data.
366    pub lltf_en: bool,
367    /// Enable to receive HT long training field(htltf) data.
368    pub htltf_en: bool,
369    /// Enable to receive space time block code HT long training
370    /// field(stbc-htltf2) data.
371    pub stbc_htltf2_en: bool,
372    /// Enable to generate htlft data by averaging lltf and ht_ltf data when
373    /// receiving HT packet. Otherwise, use ht_ltf data directly.
374    pub ltf_merge_en: bool,
375    /// Enable to turn on channel filter to smooth adjacent sub-carrier. Disable
376    /// it to keep independence of adjacent sub-carrier.
377    pub channel_filter_en: bool,
378    /// Manually scale the CSI data by left shifting or automatically scale the
379    /// CSI data. If set true, please set the shift bits. false: automatically.
380    /// true: manually.
381    pub manu_scale: bool,
382    /// Manually left shift bits of the scale of the CSI data. The range of the
383    /// left shift bits is 0~15.
384    pub shift: u8,
385    /// Enable to dump 802.11 ACK frame.
386    pub dump_ack_en: bool,
387}
388
389/// Channel state information (CSI) configuration.
390#[derive(Debug, Clone, PartialEq, Eq)]
391#[cfg_attr(feature = "defmt", derive(defmt::Format))]
392#[cfg(wifi_mac_version = "2")]
393#[instability::unstable]
394pub struct CsiConfig {
395    /// Enable to acquire CSI.
396    pub enable: u32,
397    /// Enable to acquire L-LTF when receiving a 11g PPDU.
398    pub acquire_csi_legacy: u32,
399    /// Enable to acquire HT-LTF when receiving an HT20 PPDU.
400    pub acquire_csi_ht20: u32,
401    /// Enable to acquire HT-LTF when receiving an HT40 PPDU.
402    pub acquire_csi_ht40: u32,
403    /// Enable to acquire HE-LTF when receiving an HE20 SU PPDU.
404    pub acquire_csi_su: u32,
405    /// Enable to acquire HE-LTF when receiving an HE20 MU PPDU.
406    pub acquire_csi_mu: u32,
407    /// Enable to acquire HE-LTF when receiving an HE20 DCM applied PPDU.
408    pub acquire_csi_dcm: u32,
409    /// Enable to acquire HE-LTF when receiving an HE20 Beamformed applied PPDU.
410    pub acquire_csi_beamformed: u32,
411    /// When receiving an STBC applied HE PPDU, 0- acquire the complete
412    /// HE-LTF1,  1- acquire the complete HE-LTF2, 2- sample evenly among the
413    /// HE-LTF1 and HE-LTF2.
414    pub acquire_csi_he_stbc: u32,
415    /// Value 0-3.
416    pub val_scale_cfg: u32,
417    /// Enable to dump 802.11 ACK frame, default disabled.
418    pub dump_ack_en: u32,
419    /// Reserved.
420    pub reserved: u32,
421}
422
423/// Channel state information (CSI) configuration.
424#[derive(Debug, Clone, PartialEq, Eq)]
425#[cfg_attr(feature = "defmt", derive(defmt::Format))]
426#[cfg(wifi_mac_version = "3")]
427#[instability::unstable]
428pub struct CsiConfig {
429    /// Enable to acquire CSI.
430    pub enable: u32,
431    /// Enable to acquire L-LTF.
432    pub acquire_csi_legacy: u32,
433    /// Enable to acquire L-LTF.
434    pub acquire_csi_force_lltf: bool,
435    /// Enable to acquire HT-LTF when receiving an HT20 PPDU.
436    pub acquire_csi_ht20: u32,
437    /// Enable to acquire HT-LTF when receiving an HT40 PPDU.
438    pub acquire_csi_ht40: u32,
439    /// Enable to acquire VHT-LTF when receiving an VHT20 PPDU.
440    pub acquire_csi_vht: bool,
441    /// Enable to acquire HE-LTF when receiving an HE20 SU PPDU.
442    pub acquire_csi_su: u32,
443    /// Enable to acquire HE-LTF when receiving an HE20 MU PPDU.
444    pub acquire_csi_mu: u32,
445    /// Enable to acquire HE-LTF when receiving an HE20 DCM applied PPDU.
446    pub acquire_csi_dcm: u32,
447    /// Enable to acquire HE-LTF when receiving an HE20 Beamformed applied PPDU.
448    pub acquire_csi_beamformed: u32,
449    /// When receiving an STBC applied HE PPDU, 0- acquire the complete
450    /// HE-LTF1,  1- acquire the complete HE-LTF2, 2- sample evenly among the
451    /// HE-LTF1 and HE-LTF2.
452    pub acquire_csi_he_stbc: u32,
453    /// Value 0-3.
454    pub val_scale_cfg: u32,
455    /// Enable to dump 802.11 ACK frame, default disabled.
456    pub dump_ack_en: u32,
457    /// Reserved.
458    pub reserved: u32,
459}
460
461impl CsiConfig {
462    /// Set CSI data configuration
463    pub(crate) fn apply_config(&self) -> Result<(), WifiError> {
464        let conf: wifi_csi_config_t = self.clone().into();
465        unsafe { esp_wifi_result!(esp_wifi_set_csi_config(&conf))? };
466        Ok(())
467    }
468
469    /// Register the RX callback function of CSI data. Each time a CSI data is
470    /// received, the callback function will be called.
471    pub(crate) fn set_receive_cb<C>(&mut self, cb: C) -> Result<(), WifiError>
472    where
473        C: CsiCallback,
474    {
475        let cb = Box::new(cb);
476        let cb_ptr = Box::into_raw(cb) as *mut c_void;
477
478        unsafe { esp_wifi_result!(esp_wifi_set_csi_rx_cb(Some(csi_rx_cb::<C>), cb_ptr))? };
479        Ok(())
480    }
481
482    /// Enable or disable CSI
483    pub(crate) fn set_csi(&self, enable: bool) -> Result<(), WifiError> {
484        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi.h#L1241
485        unsafe { esp_wifi_result!(esp_wifi_set_csi(enable))? };
486        Ok(())
487    }
488}
489
490impl Default for CsiConfig {
491    #[cfg(wifi_mac_version = "1")]
492    fn default() -> Self {
493        Self {
494            lltf_en: true,
495            htltf_en: true,
496            stbc_htltf2_en: true,
497            ltf_merge_en: true,
498            channel_filter_en: true,
499            manu_scale: false,
500            shift: 0,
501            dump_ack_en: false,
502        }
503    }
504
505    #[cfg(wifi_mac_version = "2")]
506    fn default() -> Self {
507        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi_he_types.h#L67-L82
508        Self {
509            enable: 1,
510            acquire_csi_legacy: 1,
511            acquire_csi_ht20: 1,
512            acquire_csi_ht40: 1,
513            acquire_csi_su: 1,
514            acquire_csi_mu: 1,
515            acquire_csi_dcm: 1,
516            acquire_csi_beamformed: 1,
517            acquire_csi_he_stbc: 2,
518            val_scale_cfg: 2,
519            dump_ack_en: 1,
520            reserved: 19,
521        }
522    }
523
524    #[cfg(wifi_mac_version = "3")]
525    fn default() -> Self {
526        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi_he_types.h
527        Self {
528            enable: 1,
529            acquire_csi_legacy: 1,
530            acquire_csi_force_lltf: true,
531            acquire_csi_ht20: 1,
532            acquire_csi_ht40: 1,
533            acquire_csi_vht: true,
534            acquire_csi_su: 1,
535            acquire_csi_mu: 1,
536            acquire_csi_dcm: 1,
537            acquire_csi_beamformed: 1,
538            acquire_csi_he_stbc: 2,
539            val_scale_cfg: 2,
540            dump_ack_en: 1,
541            reserved: 0,
542        }
543    }
544}
545
546#[doc(hidden)]
547impl From<CsiConfig> for wifi_csi_config_t {
548    fn from(config: CsiConfig) -> Self {
549        #[cfg(wifi_mac_version = "1")]
550        {
551            wifi_csi_config_t {
552                lltf_en: config.lltf_en,
553                htltf_en: config.htltf_en,
554                stbc_htltf2_en: config.stbc_htltf2_en,
555                ltf_merge_en: config.ltf_merge_en,
556                channel_filter_en: config.channel_filter_en,
557                manu_scale: config.manu_scale,
558                shift: config.shift,
559                dump_ack_en: config.dump_ack_en,
560            }
561        }
562        #[cfg(wifi_mac_version = "2")]
563        {
564            wifi_csi_acquire_config_t {
565                _bitfield_align_1: [0; 0],
566                _bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
567                    config.enable,
568                    config.acquire_csi_legacy,
569                    config.acquire_csi_ht20,
570                    config.acquire_csi_ht40,
571                    config.acquire_csi_su,
572                    config.acquire_csi_mu,
573                    config.acquire_csi_dcm,
574                    config.acquire_csi_beamformed,
575                    config.acquire_csi_he_stbc,
576                    config.val_scale_cfg,
577                    config.dump_ack_en,
578                    config.reserved,
579                ),
580            }
581        }
582        #[cfg(wifi_mac_version = "3")]
583        {
584            wifi_csi_acquire_config_t {
585                _bitfield_align_1: [0; 0],
586                _bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
587                    config.enable,
588                    config.acquire_csi_legacy,
589                    config.acquire_csi_ht20,
590                    config.acquire_csi_ht40,
591                    config.acquire_csi_su,
592                    config.acquire_csi_mu,
593                    config.acquire_csi_dcm,
594                    config.acquire_csi_beamformed,
595                    config.acquire_csi_he_stbc,
596                    config.val_scale_cfg,
597                    config.dump_ack_en,
598                    config.reserved,
599                    0, // dump_ack_en
600                    0, // lltf_bit_mode
601                    0, // reserved
602                ),
603            }
604        }
605    }
606}