1use std::collections::HashMap;
2
3use chrono::Timelike;
4use serde::{Deserialize, Serialize};
5
6use super::{
7 is_default,
8 macros::{impl_deserialize_from_empty_map_and_into_unit, impl_serialize_as_empty_map},
9};
10
11#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
13pub struct Echo<'a> {
14 pub d: &'a str,
16}
17
18#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
20pub struct EchoResponse {
21 pub r: String,
23}
24
25#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct TaskStatistics;
28impl_serialize_as_empty_map!(TaskStatistics);
29
30#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
32pub struct TaskStatisticsEntry {
33 pub prio: i32,
35 pub tid: u32,
37 pub state: u32,
39 pub stkuse: Option<u64>,
41 pub stksiz: Option<u64>,
43 pub cswcnt: Option<u64>,
45 pub runtime: Option<u64>,
47}
48
49#[derive(strum::Display, strum::AsRefStr, strum::EnumIter, Debug, Copy, Clone, PartialEq, Eq)]
51#[repr(u8)]
52#[strum(serialize_all = "snake_case")]
53pub enum ThreadStateFlags {
54 DUMMY = 1 << 0,
56
57 PENDING = 1 << 1,
59
60 SLEEPING = 1 << 2,
62
63 DEAD = 1 << 3,
65
66 SUSPENDED = 1 << 4,
68
69 ABORTING = 1 << 5,
71
72 SUSPENDING = 1 << 6,
74
75 QUEUED = 1 << 7,
77}
78
79impl ThreadStateFlags {
80 pub fn pretty_print(thread_state: u8) -> String {
82 use strum::IntoEnumIterator;
83
84 let mut bit_names = vec![];
85 for bit in Self::iter() {
86 if (thread_state & bit as u8) != 0 {
87 bit_names.push(format!("{bit}"));
88 }
89 }
90
91 bit_names.join(" | ")
92 }
93}
94
95#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
97pub struct TaskStatisticsResponse {
98 pub tasks: HashMap<String, TaskStatisticsEntry>,
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
104pub struct MemoryPoolStatistics;
105impl_serialize_as_empty_map!(MemoryPoolStatistics);
106
107const fn default_blksiz() -> u64 {
108 1
109}
110
111#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
113pub struct MemoryPoolStatisticsEntry {
114 #[serde(default = "default_blksiz")]
116 pub blksiz: u64,
117 pub nblks: u64,
119 pub nfree: u64,
121 pub min: u64,
123}
124
125#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
127pub struct MemoryPoolStatisticsResponse {
128 pub pools: HashMap<String, MemoryPoolStatisticsEntry>,
130}
131
132#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
135struct MemoryPoolStatisticsResponseZephyr4_4_0 {
136 pub tasks: HashMap<String, MemoryPoolStatisticsEntry>,
138}
139
140impl<'de> serde::Deserialize<'de> for MemoryPoolStatisticsResponse {
141 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142 where
143 D: serde::Deserializer<'de>,
144 {
145 let pools: either::Either<
146 HashMap<String, MemoryPoolStatisticsEntry>,
147 MemoryPoolStatisticsResponseZephyr4_4_0,
148 > = either::serde_untagged::deserialize(deserializer)?;
149
150 match pools {
151 either::Either::Left(pools) => Ok(Self { pools }),
152 either::Either::Right(response) => Ok(Self {
153 pools: response.tasks,
154 }),
155 }
156 }
157}
158
159fn deserialize_datetime_and_ignore_timezone<'de, D>(
161 de: D,
162) -> Result<chrono::NaiveDateTime, D::Error>
163where
164 D: serde::Deserializer<'de>,
165{
166 #[derive(Deserialize)]
167 #[serde(untagged)]
168 enum NaiveOrFixed {
169 Naive(chrono::NaiveDateTime),
170 Fixed(chrono::DateTime<chrono::FixedOffset>),
171 }
172
173 NaiveOrFixed::deserialize(de).map(|val| match val {
174 NaiveOrFixed::Naive(naive_date_time) => naive_date_time,
175 NaiveOrFixed::Fixed(date_time) => date_time.naive_local(),
176 })
177}
178
179fn serialize_datetime_for_zephyr<S>(
182 value: &chrono::NaiveDateTime,
183 serializer: S,
184) -> Result<S::Ok, S::Error>
185where
186 S: serde::Serializer,
187{
188 if value.time().nanosecond() != 0 {
189 serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S%.3f")))
190 } else {
191 serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S")))
192 }
193}
194
195#[derive(Clone, Debug, Eq, PartialEq)]
197pub struct DateTimeGet;
198impl_serialize_as_empty_map!(DateTimeGet);
199
200#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
202pub struct DateTimeGetResponse {
203 #[serde(deserialize_with = "deserialize_datetime_and_ignore_timezone")]
205 pub datetime: chrono::NaiveDateTime,
206}
207
208#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
210pub struct DateTimeSet {
211 #[serde(serialize_with = "serialize_datetime_for_zephyr")]
213 pub datetime: chrono::NaiveDateTime,
214}
215
216#[derive(Clone, Default, Debug, Eq, PartialEq)]
218pub struct DateTimeSetResponse;
219impl_deserialize_from_empty_map_and_into_unit!(DateTimeSetResponse);
220
221#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
223pub struct SystemReset {
224 #[serde(skip_serializing_if = "is_default")]
226 pub force: bool,
227 #[serde(skip_serializing_if = "Option::is_none")]
233 pub boot_mode: Option<u8>,
234}
235
236#[derive(Clone, Default, Debug, Eq, PartialEq)]
238pub struct SystemResetResponse;
239impl_deserialize_from_empty_map_and_into_unit!(SystemResetResponse);
240
241#[derive(Clone, Debug, Eq, PartialEq)]
243pub struct MCUmgrParameters;
244impl_serialize_as_empty_map!(MCUmgrParameters);
245
246#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
248pub struct MCUmgrParametersResponse {
249 pub buf_size: u32,
251 pub buf_count: u32,
253}
254
255#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
257pub struct ApplicationInfo<'a> {
258 #[serde(skip_serializing_if = "Option::is_none")]
262 pub format: Option<&'a str>,
263}
264
265#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
267pub struct ApplicationInfoResponse {
268 pub output: String,
270}
271
272#[derive(Clone, Debug, Eq, PartialEq)]
274pub struct BootloaderInfo;
275impl_serialize_as_empty_map!(BootloaderInfo);
276
277#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
279pub struct BootloaderInfoResponse {
280 pub bootloader: String,
282}
283
284#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
286#[serde(tag = "query", rename = "mode")]
287pub struct BootloaderInfoMcubootMode {}
288
289#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
291pub struct BootloaderInfoMcubootModeResponse {
292 pub mode: i32,
294 #[serde(default, rename = "no-downgrade")]
296 pub no_downgrade: bool,
297}
298
299#[cfg(test)]
300mod tests {
301 use super::super::macros::command_encode_decode_test;
302 use super::*;
303 use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
304 use ciborium::cbor;
305
306 #[test]
307 fn thread_state_flags_to_string() {
308 assert_eq!(
309 ThreadStateFlags::pretty_print(0xff),
310 "dummy | pending | sleeping | dead | suspended | aborting | suspending | queued"
311 );
312
313 assert_eq!(ThreadStateFlags::pretty_print(0b00000001), "dummy");
314 assert_eq!(ThreadStateFlags::pretty_print(0b00000010), "pending");
315 assert_eq!(ThreadStateFlags::pretty_print(0b00000100), "sleeping");
316 assert_eq!(ThreadStateFlags::pretty_print(0b00001000), "dead");
317 assert_eq!(ThreadStateFlags::pretty_print(0b00010000), "suspended");
318 assert_eq!(ThreadStateFlags::pretty_print(0b00100000), "aborting");
319 assert_eq!(ThreadStateFlags::pretty_print(0b01000000), "suspending");
320 assert_eq!(ThreadStateFlags::pretty_print(0b10000000), "queued");
321
322 assert_eq!(ThreadStateFlags::pretty_print(0), "");
323 }
324
325 command_encode_decode_test! {
326 echo,
327 (0, 0, 0),
328 Echo{d: "Hello World!"},
329 cbor!({"d" => "Hello World!"}),
330 cbor!({"r" => "Hello World!"}),
331 EchoResponse{r: "Hello World!".to_string()},
332 }
333
334 command_encode_decode_test! {
335 task_statistics_empty,
336 (0, 0, 2),
337 TaskStatistics,
338 cbor!({}),
339 cbor!({"tasks" => {}}),
340 TaskStatisticsResponse{ tasks: HashMap::new() },
341 }
342
343 command_encode_decode_test! {
344 task_statistics,
345 (0, 0, 2),
346 TaskStatistics,
347 cbor!({}),
348 cbor!({"tasks" => {
349 "task_a" => {
350 "prio" => 20,
351 "tid" => 5,
352 "state" => 10,
353 },
354 "task_b" => {
355 "prio" => 30,
356 "tid" => 31,
357 "state" => 32,
358 "stkuse" => 33,
359 "stksiz" => 34,
360 "cswcnt" => 35,
361 "runtime" => 36,
362 "last_checkin" => 0,
363 "next_checkin" => 0,
364 },
365 }}),
366 TaskStatisticsResponse{ tasks: HashMap::from([
367 (
368 "task_a".to_string(),
369 TaskStatisticsEntry{
370 prio: 20,
371 tid: 5,
372 state: 10,
373 stkuse: None,
374 stksiz: None,
375 cswcnt: None,
376 runtime: None,
377 },
378 ), (
379 "task_b".to_string(),
380 TaskStatisticsEntry{
381 prio: 30,
382 tid: 31,
383 state: 32,
384 stkuse: Some(33),
385 stksiz: Some(34),
386 cswcnt: Some(35),
387 runtime: Some(36),
388 },
389 ),
390 ]) },
391 }
392
393 command_encode_decode_test! {
394 memory_pool_statistics_empty,
395 (0, 0, 3),
396 MemoryPoolStatistics,
397 cbor!({}),
398 cbor!({}),
399 MemoryPoolStatisticsResponse{ pools: HashMap::new() },
400 }
401
402 command_encode_decode_test! {
403 memory_pool_statistics_empty_old,
404 (0, 0, 3),
405 MemoryPoolStatistics,
406 cbor!({}),
407 cbor!({"tasks" => {}}),
408 MemoryPoolStatisticsResponse{ pools: HashMap::new() },
409 }
410
411 command_encode_decode_test! {
412 memory_pool_statistics,
413 (0, 0, 3),
414 MemoryPoolStatistics,
415 cbor!({}),
416 cbor!({
417 "pool_a" => {
418 "blksiz" => 8,
419 "nblks" => 20,
420 "nfree" => 10,
421 "min" => 5,
422 },
423 "pool_b" => {
424 "nblks" => 50,
425 "nfree" => 35,
426 "min" => 30,
427 },
428 }),
429 MemoryPoolStatisticsResponse{ pools: HashMap::from([
430 (
431 "pool_a".to_string(),
432 MemoryPoolStatisticsEntry{
433 blksiz: 8,
434 nblks: 20,
435 nfree: 10,
436 min: 5,
437 },
438 ), (
439 "pool_b".to_string(),
440 MemoryPoolStatisticsEntry{
441 blksiz: 1,
442 nblks: 50,
443 nfree: 35,
444 min: 30,
445 },
446 ),
447 ]) },
448 }
449
450 command_encode_decode_test! {
451 memory_pool_statistics_old,
452 (0, 0, 3),
453 MemoryPoolStatistics,
454 cbor!({}),
455 cbor!({ "tasks" => {
456 "pool_a" => {
457 "blksiz" => 8,
458 "nblks" => 20,
459 "nfree" => 10,
460 "min" => 5,
461 },
462 "pool_b" => {
463 "nblks" => 50,
464 "nfree" => 35,
465 "min" => 30,
466 },
467 }}),
468 MemoryPoolStatisticsResponse{ pools: HashMap::from([
469 (
470 "pool_a".to_string(),
471 MemoryPoolStatisticsEntry{
472 blksiz: 8,
473 nblks: 20,
474 nfree: 10,
475 min: 5,
476 },
477 ), (
478 "pool_b".to_string(),
479 MemoryPoolStatisticsEntry{
480 blksiz: 1,
481 nblks: 50,
482 nfree: 35,
483 min: 30,
484 },
485 ),
486 ]) },
487 }
488
489 command_encode_decode_test! {
490 datetime_get_with_timezone,
491 (0, 0, 4),
492 DateTimeGet,
493 cbor!({}),
494 cbor!({
495 "datetime" => "2025-11-20T11:56:05.366345+01:00"
496 }),
497 DateTimeGetResponse{
498 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(11,56,5,366345).unwrap()),
499 },
500 }
501
502 command_encode_decode_test! {
503 datetime_get_with_millis,
504 (0, 0, 4),
505 DateTimeGet,
506 cbor!({}),
507 cbor!({
508 "datetime" => "2025-11-20T11:56:05.366"
509 }),
510 DateTimeGetResponse{
511 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_milli_opt(11,56,5,366).unwrap()),
512 },
513 }
514
515 command_encode_decode_test! {
516 datetime_get_without_millis,
517 (0, 0, 4),
518 DateTimeGet,
519 cbor!({}),
520 cbor!({
521 "datetime" => "2025-11-20T11:56:05"
522 }),
523 DateTimeGetResponse{
524 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(11,56,5).unwrap()),
525 },
526 }
527
528 command_encode_decode_test! {
529 datetime_set_with_millis,
530 (2, 0, 4),
531 DateTimeSet{
532 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(12,3,56,642133).unwrap())
533 },
534 cbor!({
535 "datetime" => "2025-11-20T12:03:56.642"
536 }),
537 cbor!({}),
538 DateTimeSetResponse,
539 }
540
541 command_encode_decode_test! {
542 datetime_set_without_millis,
543 (2, 0, 4),
544 DateTimeSet{
545 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(12,3,56).unwrap())
546 },
547 cbor!({
548 "datetime" => "2025-11-20T12:03:56"
549 }),
550 cbor!({}),
551 DateTimeSetResponse,
552 }
553
554 command_encode_decode_test! {
555 system_reset_minimal,
556 (2, 0, 5),
557 SystemReset{
558 force: false,
559 boot_mode: None,
560 },
561 cbor!({}),
562 cbor!({}),
563 SystemResetResponse,
564 }
565
566 command_encode_decode_test! {
567 system_reset_full,
568 (2, 0, 5),
569 SystemReset{
570 force: true,
571 boot_mode: Some(42),
572 },
573 cbor!({
574 "force" => true,
575 "boot_mode" => 42,
576 }),
577 cbor!({}),
578 SystemResetResponse,
579 }
580
581 command_encode_decode_test! {
582 mcumgr_parameters,
583 (0, 0, 6),
584 MCUmgrParameters,
585 cbor!({}),
586 cbor!({"buf_size" => 42, "buf_count" => 69}),
587 MCUmgrParametersResponse{buf_size: 42, buf_count: 69 },
588 }
589
590 command_encode_decode_test! {
591 application_info_without_format,
592 (0, 0, 7),
593 ApplicationInfo{
594 format: None,
595 },
596 cbor!({}),
597 cbor!({
598 "output" => "foo",
599 }),
600 ApplicationInfoResponse{
601 output: "foo".to_string(),
602 }
603 }
604
605 command_encode_decode_test! {
606 application_info_with_format,
607 (0, 0, 7),
608 ApplicationInfo{
609 format: Some("abc"),
610 },
611 cbor!({
612 "format" => "abc",
613 }),
614 cbor!({
615 "output" => "bar",
616 }),
617 ApplicationInfoResponse{
618 output: "bar".to_string(),
619 }
620 }
621
622 command_encode_decode_test! {
623 bootloader_info,
624 (0, 0, 8),
625 BootloaderInfo,
626 cbor!({}),
627 cbor!({
628 "bootloader" => "MCUboot",
629 }),
630 BootloaderInfoResponse{
631 bootloader: "MCUboot".to_string(),
632 }
633 }
634
635 command_encode_decode_test! {
636 bootloader_info_mcuboot_mode,
637 (0, 0, 8),
638 BootloaderInfoMcubootMode{},
639 cbor!({
640 "query" => "mode",
641 }),
642 cbor!({
643 "mode" => 5,
644 "no-downgrade" => true,
645 }),
646 BootloaderInfoMcubootModeResponse{
647 mode: 5,
648 no_downgrade: true,
649 }
650 }
651
652 command_encode_decode_test! {
653 bootloader_info_mcuboot_mode_default_values,
654 (0, 0, 8),
655 BootloaderInfoMcubootMode{},
656 cbor!({
657 "query" => "mode",
658 }),
659 cbor!({
660 "mode" => -1,
661 }),
662 BootloaderInfoMcubootModeResponse{
663 mode: -1,
664 no_downgrade: false,
665 }
666 }
667}