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 MemoryPoolStatisticsResponseMpools {
136 pub mpools: HashMap<String, MemoryPoolStatisticsEntry>,
138}
139
140#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
143struct MemoryPoolStatisticsResponseTasks {
144 pub tasks: HashMap<String, MemoryPoolStatisticsEntry>,
146}
147
148impl<'de> serde::Deserialize<'de> for MemoryPoolStatisticsResponse {
149 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150 where
151 D: serde::Deserializer<'de>,
152 {
153 #[derive(Deserialize)]
154 #[serde(untagged)]
155 enum ResponseVariants {
156 Mpools(MemoryPoolStatisticsResponseMpools),
157 Flat(HashMap<String, MemoryPoolStatisticsEntry>),
158 Tasks(MemoryPoolStatisticsResponseTasks),
159 }
160
161 match ResponseVariants::deserialize(deserializer)? {
162 ResponseVariants::Mpools(response) => Ok(Self {
163 pools: response.mpools,
164 }),
165 ResponseVariants::Flat(pools) => Ok(Self { pools }),
166 ResponseVariants::Tasks(response) => Ok(Self {
167 pools: response.tasks,
168 }),
169 }
170 }
171}
172
173fn deserialize_datetime_and_ignore_timezone<'de, D>(
175 de: D,
176) -> Result<chrono::NaiveDateTime, D::Error>
177where
178 D: serde::Deserializer<'de>,
179{
180 #[derive(Deserialize)]
181 #[serde(untagged)]
182 enum NaiveOrFixed {
183 Naive(chrono::NaiveDateTime),
184 Fixed(chrono::DateTime<chrono::FixedOffset>),
185 }
186
187 NaiveOrFixed::deserialize(de).map(|val| match val {
188 NaiveOrFixed::Naive(naive_date_time) => naive_date_time,
189 NaiveOrFixed::Fixed(date_time) => date_time.naive_local(),
190 })
191}
192
193fn serialize_datetime_for_zephyr<S>(
196 value: &chrono::NaiveDateTime,
197 serializer: S,
198) -> Result<S::Ok, S::Error>
199where
200 S: serde::Serializer,
201{
202 if value.time().nanosecond() != 0 {
203 serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S%.3f")))
204 } else {
205 serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S")))
206 }
207}
208
209#[derive(Clone, Debug, Eq, PartialEq)]
211pub struct DateTimeGet;
212impl_serialize_as_empty_map!(DateTimeGet);
213
214#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
216pub struct DateTimeGetResponse {
217 #[serde(deserialize_with = "deserialize_datetime_and_ignore_timezone")]
219 pub datetime: chrono::NaiveDateTime,
220}
221
222#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
224pub struct DateTimeSet {
225 #[serde(serialize_with = "serialize_datetime_for_zephyr")]
227 pub datetime: chrono::NaiveDateTime,
228}
229
230#[derive(Clone, Default, Debug, Eq, PartialEq)]
232pub struct DateTimeSetResponse;
233impl_deserialize_from_empty_map_and_into_unit!(DateTimeSetResponse);
234
235#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
237pub struct SystemReset {
238 #[serde(skip_serializing_if = "is_default")]
240 pub force: bool,
241 #[serde(skip_serializing_if = "Option::is_none")]
247 pub boot_mode: Option<u8>,
248}
249
250#[derive(Clone, Default, Debug, Eq, PartialEq)]
252pub struct SystemResetResponse;
253impl_deserialize_from_empty_map_and_into_unit!(SystemResetResponse);
254
255#[derive(Clone, Debug, Eq, PartialEq)]
257pub struct MCUmgrParameters;
258impl_serialize_as_empty_map!(MCUmgrParameters);
259
260#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
262pub struct MCUmgrParametersResponse {
263 pub buf_size: u32,
265 pub buf_count: u32,
267}
268
269#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
271pub struct ApplicationInfo<'a> {
272 #[serde(skip_serializing_if = "Option::is_none")]
276 pub format: Option<&'a str>,
277}
278
279#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
281pub struct ApplicationInfoResponse {
282 pub output: String,
284}
285
286#[derive(Clone, Debug, Eq, PartialEq)]
288pub struct BootloaderInfo;
289impl_serialize_as_empty_map!(BootloaderInfo);
290
291#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
293pub struct BootloaderInfoResponse {
294 pub bootloader: String,
296}
297
298#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
300#[serde(tag = "query", rename = "mode")]
301pub struct BootloaderInfoMcubootMode {}
302
303#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
305pub struct BootloaderInfoMcubootModeResponse {
306 pub mode: i32,
308 #[serde(default, rename = "no-downgrade")]
310 pub no_downgrade: bool,
311}
312
313#[cfg(test)]
314mod tests {
315 use super::super::macros::command_encode_decode_test;
316 use super::*;
317 use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
318 use ciborium::cbor;
319
320 #[test]
321 fn thread_state_flags_to_string() {
322 assert_eq!(
323 ThreadStateFlags::pretty_print(0xff),
324 "dummy | pending | sleeping | dead | suspended | aborting | suspending | queued"
325 );
326
327 assert_eq!(ThreadStateFlags::pretty_print(0b00000001), "dummy");
328 assert_eq!(ThreadStateFlags::pretty_print(0b00000010), "pending");
329 assert_eq!(ThreadStateFlags::pretty_print(0b00000100), "sleeping");
330 assert_eq!(ThreadStateFlags::pretty_print(0b00001000), "dead");
331 assert_eq!(ThreadStateFlags::pretty_print(0b00010000), "suspended");
332 assert_eq!(ThreadStateFlags::pretty_print(0b00100000), "aborting");
333 assert_eq!(ThreadStateFlags::pretty_print(0b01000000), "suspending");
334 assert_eq!(ThreadStateFlags::pretty_print(0b10000000), "queued");
335
336 assert_eq!(ThreadStateFlags::pretty_print(0), "");
337 }
338
339 command_encode_decode_test! {
340 echo,
341 (0, 0, 0),
342 Echo{d: "Hello World!"},
343 cbor!({"d" => "Hello World!"}),
344 cbor!({"r" => "Hello World!"}),
345 EchoResponse{r: "Hello World!".to_string()},
346 }
347
348 command_encode_decode_test! {
349 task_statistics_empty,
350 (0, 0, 2),
351 TaskStatistics,
352 cbor!({}),
353 cbor!({"tasks" => {}}),
354 TaskStatisticsResponse{ tasks: HashMap::new() },
355 }
356
357 command_encode_decode_test! {
358 task_statistics,
359 (0, 0, 2),
360 TaskStatistics,
361 cbor!({}),
362 cbor!({"tasks" => {
363 "task_a" => {
364 "prio" => 20,
365 "tid" => 5,
366 "state" => 10,
367 },
368 "task_b" => {
369 "prio" => 30,
370 "tid" => 31,
371 "state" => 32,
372 "stkuse" => 33,
373 "stksiz" => 34,
374 "cswcnt" => 35,
375 "runtime" => 36,
376 "last_checkin" => 0,
377 "next_checkin" => 0,
378 },
379 }}),
380 TaskStatisticsResponse{ tasks: HashMap::from([
381 (
382 "task_a".to_string(),
383 TaskStatisticsEntry{
384 prio: 20,
385 tid: 5,
386 state: 10,
387 stkuse: None,
388 stksiz: None,
389 cswcnt: None,
390 runtime: None,
391 },
392 ), (
393 "task_b".to_string(),
394 TaskStatisticsEntry{
395 prio: 30,
396 tid: 31,
397 state: 32,
398 stkuse: Some(33),
399 stksiz: Some(34),
400 cswcnt: Some(35),
401 runtime: Some(36),
402 },
403 ),
404 ]) },
405 }
406
407 command_encode_decode_test! {
408 memory_pool_statistics_empty,
409 (0, 0, 3),
410 MemoryPoolStatistics,
411 cbor!({}),
412 cbor!({"mpools" => {}}),
413 MemoryPoolStatisticsResponse{ pools: HashMap::new() },
414 }
415
416 command_encode_decode_test! {
417 memory_pool_statistics_empty_old_1,
418 (0, 0, 3),
419 MemoryPoolStatistics,
420 cbor!({}),
421 cbor!({}),
422 MemoryPoolStatisticsResponse{ pools: HashMap::new() },
423 }
424
425 command_encode_decode_test! {
426 memory_pool_statistics_empty_old_2,
427 (0, 0, 3),
428 MemoryPoolStatistics,
429 cbor!({}),
430 cbor!({"tasks" => {}}),
431 MemoryPoolStatisticsResponse{ pools: HashMap::new() },
432 }
433
434 command_encode_decode_test! {
435 memory_pool_statistics,
436 (0, 0, 3),
437 MemoryPoolStatistics,
438 cbor!({}),
439 cbor!({ "mpools" => {
440 "pool_a" => {
441 "blksiz" => 8,
442 "nblks" => 20,
443 "nfree" => 10,
444 "min" => 5,
445 },
446 "pool_b" => {
447 "nblks" => 50,
448 "nfree" => 35,
449 "min" => 30,
450 },
451 }}),
452 MemoryPoolStatisticsResponse{ pools: HashMap::from([
453 (
454 "pool_a".to_string(),
455 MemoryPoolStatisticsEntry{
456 blksiz: 8,
457 nblks: 20,
458 nfree: 10,
459 min: 5,
460 },
461 ), (
462 "pool_b".to_string(),
463 MemoryPoolStatisticsEntry{
464 blksiz: 1,
465 nblks: 50,
466 nfree: 35,
467 min: 30,
468 },
469 ),
470 ]) },
471 }
472
473 command_encode_decode_test! {
474 memory_pool_statistics_old_1,
475 (0, 0, 3),
476 MemoryPoolStatistics,
477 cbor!({}),
478 cbor!({
479 "pool_a" => {
480 "blksiz" => 8,
481 "nblks" => 20,
482 "nfree" => 10,
483 "min" => 5,
484 },
485 "pool_b" => {
486 "nblks" => 50,
487 "nfree" => 35,
488 "min" => 30,
489 },
490 }),
491 MemoryPoolStatisticsResponse{ pools: HashMap::from([
492 (
493 "pool_a".to_string(),
494 MemoryPoolStatisticsEntry{
495 blksiz: 8,
496 nblks: 20,
497 nfree: 10,
498 min: 5,
499 },
500 ), (
501 "pool_b".to_string(),
502 MemoryPoolStatisticsEntry{
503 blksiz: 1,
504 nblks: 50,
505 nfree: 35,
506 min: 30,
507 },
508 ),
509 ]) },
510 }
511
512 command_encode_decode_test! {
513 memory_pool_statistics_old_2,
514 (0, 0, 3),
515 MemoryPoolStatistics,
516 cbor!({}),
517 cbor!({ "tasks" => {
518 "pool_a" => {
519 "blksiz" => 8,
520 "nblks" => 20,
521 "nfree" => 10,
522 "min" => 5,
523 },
524 "pool_b" => {
525 "nblks" => 50,
526 "nfree" => 35,
527 "min" => 30,
528 },
529 }}),
530 MemoryPoolStatisticsResponse{ pools: HashMap::from([
531 (
532 "pool_a".to_string(),
533 MemoryPoolStatisticsEntry{
534 blksiz: 8,
535 nblks: 20,
536 nfree: 10,
537 min: 5,
538 },
539 ), (
540 "pool_b".to_string(),
541 MemoryPoolStatisticsEntry{
542 blksiz: 1,
543 nblks: 50,
544 nfree: 35,
545 min: 30,
546 },
547 ),
548 ]) },
549 }
550
551 command_encode_decode_test! {
552 datetime_get_with_timezone,
553 (0, 0, 4),
554 DateTimeGet,
555 cbor!({}),
556 cbor!({
557 "datetime" => "2025-11-20T11:56:05.366345+01:00"
558 }),
559 DateTimeGetResponse{
560 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(11,56,5,366345).unwrap()),
561 },
562 }
563
564 command_encode_decode_test! {
565 datetime_get_with_millis,
566 (0, 0, 4),
567 DateTimeGet,
568 cbor!({}),
569 cbor!({
570 "datetime" => "2025-11-20T11:56:05.366"
571 }),
572 DateTimeGetResponse{
573 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_milli_opt(11,56,5,366).unwrap()),
574 },
575 }
576
577 command_encode_decode_test! {
578 datetime_get_without_millis,
579 (0, 0, 4),
580 DateTimeGet,
581 cbor!({}),
582 cbor!({
583 "datetime" => "2025-11-20T11:56:05"
584 }),
585 DateTimeGetResponse{
586 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(11,56,5).unwrap()),
587 },
588 }
589
590 command_encode_decode_test! {
591 datetime_set_with_millis,
592 (2, 0, 4),
593 DateTimeSet{
594 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(12,3,56,642133).unwrap())
595 },
596 cbor!({
597 "datetime" => "2025-11-20T12:03:56.642"
598 }),
599 cbor!({}),
600 DateTimeSetResponse,
601 }
602
603 command_encode_decode_test! {
604 datetime_set_without_millis,
605 (2, 0, 4),
606 DateTimeSet{
607 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(12,3,56).unwrap())
608 },
609 cbor!({
610 "datetime" => "2025-11-20T12:03:56"
611 }),
612 cbor!({}),
613 DateTimeSetResponse,
614 }
615
616 command_encode_decode_test! {
617 system_reset_minimal,
618 (2, 0, 5),
619 SystemReset{
620 force: false,
621 boot_mode: None,
622 },
623 cbor!({}),
624 cbor!({}),
625 SystemResetResponse,
626 }
627
628 command_encode_decode_test! {
629 system_reset_full,
630 (2, 0, 5),
631 SystemReset{
632 force: true,
633 boot_mode: Some(42),
634 },
635 cbor!({
636 "force" => true,
637 "boot_mode" => 42,
638 }),
639 cbor!({}),
640 SystemResetResponse,
641 }
642
643 command_encode_decode_test! {
644 mcumgr_parameters,
645 (0, 0, 6),
646 MCUmgrParameters,
647 cbor!({}),
648 cbor!({"buf_size" => 42, "buf_count" => 69}),
649 MCUmgrParametersResponse{buf_size: 42, buf_count: 69 },
650 }
651
652 command_encode_decode_test! {
653 application_info_without_format,
654 (0, 0, 7),
655 ApplicationInfo{
656 format: None,
657 },
658 cbor!({}),
659 cbor!({
660 "output" => "foo",
661 }),
662 ApplicationInfoResponse{
663 output: "foo".to_string(),
664 }
665 }
666
667 command_encode_decode_test! {
668 application_info_with_format,
669 (0, 0, 7),
670 ApplicationInfo{
671 format: Some("abc"),
672 },
673 cbor!({
674 "format" => "abc",
675 }),
676 cbor!({
677 "output" => "bar",
678 }),
679 ApplicationInfoResponse{
680 output: "bar".to_string(),
681 }
682 }
683
684 command_encode_decode_test! {
685 bootloader_info,
686 (0, 0, 8),
687 BootloaderInfo,
688 cbor!({}),
689 cbor!({
690 "bootloader" => "MCUboot",
691 }),
692 BootloaderInfoResponse{
693 bootloader: "MCUboot".to_string(),
694 }
695 }
696
697 command_encode_decode_test! {
698 bootloader_info_mcuboot_mode,
699 (0, 0, 8),
700 BootloaderInfoMcubootMode{},
701 cbor!({
702 "query" => "mode",
703 }),
704 cbor!({
705 "mode" => 5,
706 "no-downgrade" => true,
707 }),
708 BootloaderInfoMcubootModeResponse{
709 mode: 5,
710 no_downgrade: true,
711 }
712 }
713
714 command_encode_decode_test! {
715 bootloader_info_mcuboot_mode_default_values,
716 (0, 0, 8),
717 BootloaderInfoMcubootMode{},
718 cbor!({
719 "query" => "mode",
720 }),
721 cbor!({
722 "mode" => -1,
723 }),
724 BootloaderInfoMcubootModeResponse{
725 mode: -1,
726 no_downgrade: false,
727 }
728 }
729}