use std::collections::HashMap;
use chrono::Timelike;
use serde::{Deserialize, Serialize};
use super::{
is_default,
macros::{impl_deserialize_from_empty_map_and_into_unit, impl_serialize_as_empty_map},
};
#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
pub struct Echo<'a> {
pub d: &'a str,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct EchoResponse {
pub r: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TaskStatistics;
impl_serialize_as_empty_map!(TaskStatistics);
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct TaskStatisticsEntry {
pub prio: i32,
pub tid: u32,
pub state: u32,
pub stkuse: Option<u64>,
pub stksiz: Option<u64>,
pub cswcnt: Option<u64>,
pub runtime: Option<u64>,
}
#[derive(strum::Display, strum::AsRefStr, strum::EnumIter, Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
#[strum(serialize_all = "snake_case")]
pub enum ThreadStateFlags {
DUMMY = 1 << 0,
PENDING = 1 << 1,
SLEEPING = 1 << 2,
DEAD = 1 << 3,
SUSPENDED = 1 << 4,
ABORTING = 1 << 5,
SUSPENDING = 1 << 6,
QUEUED = 1 << 7,
}
impl ThreadStateFlags {
pub fn pretty_print(thread_state: u8) -> String {
use strum::IntoEnumIterator;
let mut bit_names = vec![];
for bit in Self::iter() {
if (thread_state & bit as u8) != 0 {
bit_names.push(format!("{bit}"));
}
}
bit_names.join(" | ")
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct TaskStatisticsResponse {
pub tasks: HashMap<String, TaskStatisticsEntry>,
}
fn deserialize_datetime_and_ignore_timezone<'de, D>(
de: D,
) -> Result<chrono::NaiveDateTime, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum NaiveOrFixed {
Naive(chrono::NaiveDateTime),
Fixed(chrono::DateTime<chrono::FixedOffset>),
}
NaiveOrFixed::deserialize(de).map(|val| match val {
NaiveOrFixed::Naive(naive_date_time) => naive_date_time,
NaiveOrFixed::Fixed(date_time) => date_time.naive_local(),
})
}
fn serialize_datetime_for_zephyr<S>(
value: &chrono::NaiveDateTime,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if value.time().nanosecond() != 0 {
serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S%.3f")))
} else {
serializer.serialize_str(&format!("{}", value.format("%Y-%m-%dT%H:%M:%S")))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DateTimeGet;
impl_serialize_as_empty_map!(DateTimeGet);
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct DateTimeGetResponse {
#[serde(deserialize_with = "deserialize_datetime_and_ignore_timezone")]
pub datetime: chrono::NaiveDateTime,
}
#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
pub struct DateTimeSet {
#[serde(serialize_with = "serialize_datetime_for_zephyr")]
pub datetime: chrono::NaiveDateTime,
}
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct DateTimeSetResponse;
impl_deserialize_from_empty_map_and_into_unit!(DateTimeSetResponse);
#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
pub struct SystemReset {
#[serde(skip_serializing_if = "is_default")]
pub force: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub boot_mode: Option<u8>,
}
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct SystemResetResponse;
impl_deserialize_from_empty_map_and_into_unit!(SystemResetResponse);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MCUmgrParameters;
impl_serialize_as_empty_map!(MCUmgrParameters);
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct MCUmgrParametersResponse {
pub buf_size: u32,
pub buf_count: u32,
}
#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
pub struct ApplicationInfo<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<&'a str>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct ApplicationInfoResponse {
pub output: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BootloaderInfo;
impl_serialize_as_empty_map!(BootloaderInfo);
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct BootloaderInfoResponse {
pub bootloader: String,
}
#[derive(Clone, Serialize, Debug, Eq, PartialEq)]
#[serde(tag = "query", rename = "mode")]
pub struct BootloaderInfoMcubootMode {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct BootloaderInfoMcubootModeResponse {
pub mode: i32,
#[serde(default, rename = "no-downgrade")]
pub no_downgrade: bool,
}
#[cfg(test)]
mod tests {
use super::super::macros::command_encode_decode_test;
use super::*;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use ciborium::cbor;
#[test]
fn thread_state_flags_to_string() {
assert_eq!(
ThreadStateFlags::pretty_print(0xff),
"dummy | pending | sleeping | dead | suspended | aborting | suspending | queued"
);
assert_eq!(ThreadStateFlags::pretty_print(0b00000001), "dummy");
assert_eq!(ThreadStateFlags::pretty_print(0b00000010), "pending");
assert_eq!(ThreadStateFlags::pretty_print(0b00000100), "sleeping");
assert_eq!(ThreadStateFlags::pretty_print(0b00001000), "dead");
assert_eq!(ThreadStateFlags::pretty_print(0b00010000), "suspended");
assert_eq!(ThreadStateFlags::pretty_print(0b00100000), "aborting");
assert_eq!(ThreadStateFlags::pretty_print(0b01000000), "suspending");
assert_eq!(ThreadStateFlags::pretty_print(0b10000000), "queued");
assert_eq!(ThreadStateFlags::pretty_print(0), "");
}
command_encode_decode_test! {
echo,
(0, 0, 0),
Echo{d: "Hello World!"},
cbor!({"d" => "Hello World!"}),
cbor!({"r" => "Hello World!"}),
EchoResponse{r: "Hello World!".to_string()},
}
command_encode_decode_test! {
task_statistics_empty,
(0, 0, 2),
TaskStatistics,
cbor!({}),
cbor!({"tasks" => {}}),
TaskStatisticsResponse{ tasks: HashMap::new() },
}
command_encode_decode_test! {
task_statistics,
(0, 0, 2),
TaskStatistics,
cbor!({}),
cbor!({"tasks" => {
"task_a" => {
"prio" => 20,
"tid" => 5,
"state" => 10,
},
"task_b" => {
"prio" => 30,
"tid" => 31,
"state" => 32,
"stkuse" => 33,
"stksiz" => 34,
"cswcnt" => 35,
"runtime" => 36,
"last_checkin" => 0,
"next_checkin" => 0,
},
}}),
TaskStatisticsResponse{ tasks: HashMap::from([
(
"task_a".to_string(),
TaskStatisticsEntry{
prio: 20,
tid: 5,
state: 10,
stkuse: None,
stksiz: None,
cswcnt: None,
runtime: None,
},
), (
"task_b".to_string(),
TaskStatisticsEntry{
prio: 30,
tid: 31,
state: 32,
stkuse: Some(33),
stksiz: Some(34),
cswcnt: Some(35),
runtime: Some(36),
},
),
]) },
}
command_encode_decode_test! {
datetime_get_with_timezone,
(0, 0, 4),
DateTimeGet,
cbor!({}),
cbor!({
"datetime" => "2025-11-20T11:56:05.366345+01:00"
}),
DateTimeGetResponse{
datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(11,56,5,366345).unwrap()),
},
}
command_encode_decode_test! {
datetime_get_with_millis,
(0, 0, 4),
DateTimeGet,
cbor!({}),
cbor!({
"datetime" => "2025-11-20T11:56:05.366"
}),
DateTimeGetResponse{
datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_milli_opt(11,56,5,366).unwrap()),
},
}
command_encode_decode_test! {
datetime_get_without_millis,
(0, 0, 4),
DateTimeGet,
cbor!({}),
cbor!({
"datetime" => "2025-11-20T11:56:05"
}),
DateTimeGetResponse{
datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(11,56,5).unwrap()),
},
}
command_encode_decode_test! {
datetime_set_with_millis,
(2, 0, 4),
DateTimeSet{
datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_micro_opt(12,3,56,642133).unwrap())
},
cbor!({
"datetime" => "2025-11-20T12:03:56.642"
}),
cbor!({}),
DateTimeSetResponse,
}
command_encode_decode_test! {
datetime_set_without_millis,
(2, 0, 4),
DateTimeSet{
datetime: NaiveDateTime::new(NaiveDate::from_ymd_opt(2025, 11, 20).unwrap(), NaiveTime::from_hms_opt(12,3,56).unwrap())
},
cbor!({
"datetime" => "2025-11-20T12:03:56"
}),
cbor!({}),
DateTimeSetResponse,
}
command_encode_decode_test! {
system_reset_minimal,
(2, 0, 5),
SystemReset{
force: false,
boot_mode: None,
},
cbor!({}),
cbor!({}),
SystemResetResponse,
}
command_encode_decode_test! {
system_reset_full,
(2, 0, 5),
SystemReset{
force: true,
boot_mode: Some(42),
},
cbor!({
"force" => true,
"boot_mode" => 42,
}),
cbor!({}),
SystemResetResponse,
}
command_encode_decode_test! {
mcumgr_parameters,
(0, 0, 6),
MCUmgrParameters,
cbor!({}),
cbor!({"buf_size" => 42, "buf_count" => 69}),
MCUmgrParametersResponse{buf_size: 42, buf_count: 69 },
}
command_encode_decode_test! {
application_info_without_format,
(0, 0, 7),
ApplicationInfo{
format: None,
},
cbor!({}),
cbor!({
"output" => "foo",
}),
ApplicationInfoResponse{
output: "foo".to_string(),
}
}
command_encode_decode_test! {
application_info_with_format,
(0, 0, 7),
ApplicationInfo{
format: Some("abc"),
},
cbor!({
"format" => "abc",
}),
cbor!({
"output" => "bar",
}),
ApplicationInfoResponse{
output: "bar".to_string(),
}
}
command_encode_decode_test! {
bootloader_info,
(0, 0, 8),
BootloaderInfo,
cbor!({}),
cbor!({
"bootloader" => "MCUboot",
}),
BootloaderInfoResponse{
bootloader: "MCUboot".to_string(),
}
}
command_encode_decode_test! {
bootloader_info_mcuboot_mode,
(0, 0, 8),
BootloaderInfoMcubootMode{},
cbor!({
"query" => "mode",
}),
cbor!({
"mode" => 5,
"no-downgrade" => true,
}),
BootloaderInfoMcubootModeResponse{
mode: 5,
no_downgrade: true,
}
}
command_encode_decode_test! {
bootloader_info_mcuboot_mode_default_values,
(0, 0, 8),
BootloaderInfoMcubootMode{},
cbor!({
"query" => "mode",
}),
cbor!({
"mode" => -1,
}),
BootloaderInfoMcubootModeResponse{
mode: -1,
no_downgrade: false,
}
}
}