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(Debug, Serialize, Eq, PartialEq)]
13pub struct Echo<'a> {
14 pub d: &'a str,
16}
17
18#[derive(Debug, Deserialize, Eq, PartialEq)]
20pub struct EchoResponse {
21 pub r: String,
23}
24
25#[derive(Debug, Eq, PartialEq)]
27pub struct TaskStatistics;
28impl_serialize_as_empty_map!(TaskStatistics);
29
30#[derive(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(Debug, Deserialize, Eq, PartialEq)]
97pub struct TaskStatisticsResponse {
98 pub tasks: HashMap<String, TaskStatisticsEntry>,
100}
101
102fn deserialize_datetime_and_ignore_timezone<'de, D>(
104 de: D,
105) -> Result<chrono::NaiveDateTime, D::Error>
106where
107 D: serde::Deserializer<'de>,
108{
109 #[derive(Deserialize)]
110 #[serde(untagged)]
111 enum NaiveOrFixed {
112 Naive(chrono::NaiveDateTime),
113 Fixed(chrono::DateTime<chrono::FixedOffset>),
114 }
115
116 NaiveOrFixed::deserialize(de).map(|val| match val {
117 NaiveOrFixed::Naive(naive_date_time) => naive_date_time,
118 NaiveOrFixed::Fixed(date_time) => date_time.naive_local(),
119 })
120}
121
122fn serialize_datetime_for_zephyr<S>(
125 value: &chrono::NaiveDateTime,
126 serializer: S,
127) -> Result<S::Ok, S::Error>
128where
129 S: serde::Serializer,
130{
131 if value.time().nanosecond() != 0 {
132 serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S%.3f")))
133 } else {
134 serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S")))
135 }
136}
137
138#[derive(Debug, Eq, PartialEq)]
140pub struct DateTimeGet;
141impl_serialize_as_empty_map!(DateTimeGet);
142
143#[derive(Debug, Deserialize, Eq, PartialEq)]
145pub struct DateTimeGetResponse {
146 #[serde(deserialize_with = "deserialize_datetime_and_ignore_timezone")]
148 pub datetime: chrono::NaiveDateTime,
149}
150
151#[derive(Serialize, Debug, Eq, PartialEq)]
153pub struct DateTimeSet {
154 #[serde(serialize_with = "serialize_datetime_for_zephyr")]
156 pub datetime: chrono::NaiveDateTime,
157}
158
159#[derive(Default, Debug, Eq, PartialEq)]
161pub struct DateTimeSetResponse;
162impl_deserialize_from_empty_map_and_into_unit!(DateTimeSetResponse);
163
164#[derive(Serialize, Debug, Eq, PartialEq)]
166pub struct SystemReset {
167 #[serde(skip_serializing_if = "is_default")]
169 pub force: bool,
170 #[serde(skip_serializing_if = "Option::is_none")]
176 pub boot_mode: Option<u8>,
177}
178
179#[derive(Default, Debug, Eq, PartialEq)]
181pub struct SystemResetResponse;
182impl_deserialize_from_empty_map_and_into_unit!(SystemResetResponse);
183
184#[derive(Debug, Eq, PartialEq)]
186pub struct MCUmgrParameters;
187impl_serialize_as_empty_map!(MCUmgrParameters);
188
189#[derive(Debug, Deserialize, Eq, PartialEq)]
191pub struct MCUmgrParametersResponse {
192 pub buf_size: u32,
194 pub buf_count: u32,
196}
197
198#[derive(Serialize, Debug, Eq, PartialEq)]
200pub struct ApplicationInfo<'a> {
201 #[serde(skip_serializing_if = "Option::is_none")]
205 pub format: Option<&'a str>,
206}
207
208#[derive(Debug, Deserialize, Eq, PartialEq)]
210pub struct ApplicationInfoResponse {
211 pub output: String,
213}
214
215#[derive(Debug, Eq, PartialEq)]
217pub struct BootloaderInfo;
218impl_serialize_as_empty_map!(BootloaderInfo);
219
220#[derive(Debug, Deserialize, Eq, PartialEq)]
222pub struct BootloaderInfoResponse {
223 pub bootloader: String,
225}
226
227#[derive(Serialize, Debug, Eq, PartialEq)]
229#[serde(tag = "query", rename = "mode")]
230pub struct BootloaderInfoMcubootMode {}
231
232#[derive(Debug, Deserialize, Eq, PartialEq)]
234pub struct BootloaderInfoMcubootModeResponse {
235 pub mode: i32,
237 #[serde(default, rename = "no-downgrade")]
239 pub no_downgrade: bool,
240}
241
242#[cfg(test)]
243mod tests {
244 use super::super::macros::command_encode_decode_test;
245 use super::*;
246 use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
247 use ciborium::cbor;
248
249 #[test]
250 fn thread_state_flags_to_string() {
251 assert_eq!(
252 ThreadStateFlags::pretty_print(0xff),
253 "dummy | pending | sleeping | dead | suspended | aborting | suspending | queued"
254 );
255
256 assert_eq!(ThreadStateFlags::pretty_print(0b00000001), "dummy");
257 assert_eq!(ThreadStateFlags::pretty_print(0b00000010), "pending");
258 assert_eq!(ThreadStateFlags::pretty_print(0b00000100), "sleeping");
259 assert_eq!(ThreadStateFlags::pretty_print(0b00001000), "dead");
260 assert_eq!(ThreadStateFlags::pretty_print(0b00010000), "suspended");
261 assert_eq!(ThreadStateFlags::pretty_print(0b00100000), "aborting");
262 assert_eq!(ThreadStateFlags::pretty_print(0b01000000), "suspending");
263 assert_eq!(ThreadStateFlags::pretty_print(0b10000000), "queued");
264
265 assert_eq!(ThreadStateFlags::pretty_print(0), "");
266 }
267
268 command_encode_decode_test! {
269 echo,
270 (0, 0, 0),
271 Echo{d: "Hello World!"},
272 cbor!({"d" => "Hello World!"}),
273 cbor!({"r" => "Hello World!"}),
274 EchoResponse{r: "Hello World!".to_string()},
275 }
276
277 command_encode_decode_test! {
278 task_statistics_empty,
279 (0, 0, 2),
280 TaskStatistics,
281 cbor!({}),
282 cbor!({"tasks" => {}}),
283 TaskStatisticsResponse{ tasks: HashMap::new() },
284 }
285
286 command_encode_decode_test! {
287 task_statistics,
288 (0, 0, 2),
289 TaskStatistics,
290 cbor!({}),
291 cbor!({"tasks" => {
292 "task_a" => {
293 "prio" => 20,
294 "tid" => 5,
295 "state" => 10,
296 },
297 "task_b" => {
298 "prio" => 30,
299 "tid" => 31,
300 "state" => 32,
301 "stkuse" => 33,
302 "stksiz" => 34,
303 "cswcnt" => 35,
304 "runtime" => 36,
305 "last_checkin" => 0,
306 "next_checkin" => 0,
307 },
308 }}),
309 TaskStatisticsResponse{ tasks: HashMap::from([
310 (
311 "task_a".to_string(),
312 TaskStatisticsEntry{
313 prio: 20,
314 tid: 5,
315 state: 10,
316 stkuse: None,
317 stksiz: None,
318 cswcnt: None,
319 runtime: None,
320 },
321 ), (
322 "task_b".to_string(),
323 TaskStatisticsEntry{
324 prio: 30,
325 tid: 31,
326 state: 32,
327 stkuse: Some(33),
328 stksiz: Some(34),
329 cswcnt: Some(35),
330 runtime: Some(36),
331 },
332 ),
333 ]) },
334 }
335
336 command_encode_decode_test! {
337 datetime_get_with_timezone,
338 (0, 0, 4),
339 DateTimeGet,
340 cbor!({}),
341 cbor!({
342 "datetime" => "2025-11-20T11:56:05.366345+01:00"
343 }),
344 DateTimeGetResponse{
345 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(11,56,5,366345).unwrap()),
346 },
347 }
348
349 command_encode_decode_test! {
350 datetime_get_with_millis,
351 (0, 0, 4),
352 DateTimeGet,
353 cbor!({}),
354 cbor!({
355 "datetime" => "2025-11-20T11:56:05.366"
356 }),
357 DateTimeGetResponse{
358 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_milli_opt(11,56,5,366).unwrap()),
359 },
360 }
361
362 command_encode_decode_test! {
363 datetime_get_without_millis,
364 (0, 0, 4),
365 DateTimeGet,
366 cbor!({}),
367 cbor!({
368 "datetime" => "2025-11-20T11:56:05"
369 }),
370 DateTimeGetResponse{
371 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(11,56,5).unwrap()),
372 },
373 }
374
375 command_encode_decode_test! {
376 datetime_set_with_millis,
377 (2, 0, 4),
378 DateTimeSet{
379 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(12,3,56,642133).unwrap())
380 },
381 cbor!({
382 "datetime" => "2025-11-20T12:03:56.642"
383 }),
384 cbor!({}),
385 DateTimeSetResponse,
386 }
387
388 command_encode_decode_test! {
389 datetime_set_without_millis,
390 (2, 0, 4),
391 DateTimeSet{
392 datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(12,3,56).unwrap())
393 },
394 cbor!({
395 "datetime" => "2025-11-20T12:03:56"
396 }),
397 cbor!({}),
398 DateTimeSetResponse,
399 }
400
401 command_encode_decode_test! {
402 system_reset_minimal,
403 (2, 0, 5),
404 SystemReset{
405 force: false,
406 boot_mode: None,
407 },
408 cbor!({}),
409 cbor!({}),
410 SystemResetResponse,
411 }
412
413 command_encode_decode_test! {
414 system_reset_full,
415 (2, 0, 5),
416 SystemReset{
417 force: true,
418 boot_mode: Some(42),
419 },
420 cbor!({
421 "force" => true,
422 "boot_mode" => 42,
423 }),
424 cbor!({}),
425 SystemResetResponse,
426 }
427
428 command_encode_decode_test! {
429 mcumgr_parameters,
430 (0, 0, 6),
431 MCUmgrParameters,
432 cbor!({}),
433 cbor!({"buf_size" => 42, "buf_count" => 69}),
434 MCUmgrParametersResponse{buf_size: 42, buf_count: 69 },
435 }
436
437 command_encode_decode_test! {
438 application_info_without_format,
439 (0, 0, 7),
440 ApplicationInfo{
441 format: None,
442 },
443 cbor!({}),
444 cbor!({
445 "output" => "foo",
446 }),
447 ApplicationInfoResponse{
448 output: "foo".to_string(),
449 }
450 }
451
452 command_encode_decode_test! {
453 application_info_with_format,
454 (0, 0, 7),
455 ApplicationInfo{
456 format: Some("abc"),
457 },
458 cbor!({
459 "format" => "abc",
460 }),
461 cbor!({
462 "output" => "bar",
463 }),
464 ApplicationInfoResponse{
465 output: "bar".to_string(),
466 }
467 }
468
469 command_encode_decode_test! {
470 bootloader_info,
471 (0, 0, 8),
472 BootloaderInfo,
473 cbor!({}),
474 cbor!({
475 "bootloader" => "MCUboot",
476 }),
477 BootloaderInfoResponse{
478 bootloader: "MCUboot".to_string(),
479 }
480 }
481
482 command_encode_decode_test! {
483 bootloader_info_mcuboot_mode,
484 (0, 0, 8),
485 BootloaderInfoMcubootMode{},
486 cbor!({
487 "query" => "mode",
488 }),
489 cbor!({
490 "mode" => 5,
491 "no-downgrade" => true,
492 }),
493 BootloaderInfoMcubootModeResponse{
494 mode: 5,
495 no_downgrade: true,
496 }
497 }
498
499 command_encode_decode_test! {
500 bootloader_info_mcuboot_mode_default_values,
501 (0, 0, 8),
502 BootloaderInfoMcubootMode{},
503 cbor!({
504 "query" => "mode",
505 }),
506 cbor!({
507 "mode" => -1,
508 }),
509 BootloaderInfoMcubootModeResponse{
510 mode: -1,
511 no_downgrade: false,
512 }
513 }
514}