Use pydantic #23

Merged
muellerr merged 4 commits from use-pydantic-for-python-serialization into main 2024-04-29 15:24:49 +02:00
11 changed files with 130 additions and 131 deletions
Showing only changes of commit 56b5076230 - Show all commits

2
Cargo.lock generated
View File

@ -629,7 +629,7 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "ops-sat-rs" name = "ops-sat-rs"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"derive-new", "derive-new",

View File

@ -0,0 +1,20 @@
import enum
from pydantic import BaseModel
class ActionId(enum.IntEnum):
DEFAULT_SINGLE = 1
BALANCED_SINGLE = 2
DEFAULT_SINGLE_FLATSAT = 3
BALANCED_SNGLE_FLATSAT = 4
CUSTOM_PARAMS = 5
class CameraParameters(BaseModel):
R: int
G: int
B: int
N: int
P: bool
E: int
W: int

View File

@ -1,17 +0,0 @@
import struct
from serde import Model, fields
from opssat_tmtc.common import EXPERIMENT_APID, UniqueId, make_unique_id
class CameraParameters(Model):
R: fields.Int()
G: fields.Int()
B: fields.Int()
N: fields.Int()
P: fields.Bool()
E: fields.Int()
W: fields.Int()
def serialize_for_uplink(self) -> bytearray:
return self.to_json().encode("utf-8")

View File

@ -24,7 +24,6 @@ class UniqueId(enum.IntEnum):
class EventSeverity(enum.IntEnum): class EventSeverity(enum.IntEnum):
INFO = 0 INFO = 0
LOW = 1 LOW = 1
MEDIUM = 2 MEDIUM = 2

View File

@ -37,7 +37,6 @@ def create_set_mode_cmd(
def create_cmd_definition_tree() -> CmdTreeNode: def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node() root_node = CmdTreeNode.root_node()
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True) hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)

View File

@ -14,12 +14,15 @@ authors = [
] ]
dependencies = [ dependencies = [
"tmtccmd==8.0.0rc.2", "tmtccmd==8.0.0rc.2",
"serde==0.9.0" "serde==0.9.0",
"pydantic==2.7.1"
] ]
[tool.setuptools.packages] [tool.setuptools.packages]
find = {} find = {}
[tool.ruff]
extend-exclude = ["archive"]
[tool.ruff.lint] [tool.ruff.lint]
ignore = ["E501"] ignore = ["E501"]
[tool.ruff.lint.extend-per-file-ignores] [tool.ruff.lint.extend-per-file-ignores]

0
pytmtc/tests/__init__.py Normal file
View File

27
pytmtc/tests/test_cam.py Normal file
View File

@ -0,0 +1,27 @@
from unittest import TestCase
from opssat_tmtc.camera_params import CameraParameters
TEST_CAM_PARAMS = CameraParameters(R=8, G=8, B=8, N=1, P=True, E=200, W=1000)
EXPECTED_JSON = '{"R":8,"G":8,"B":8,"N":1,"P":true,"E":200,"W":1000}'
class TestCamInterface(TestCase):
def test_serialization_to_dict(self):
model = TEST_CAM_PARAMS.model_dump()
self.assertEqual(model["R"], 8)
self.assertEqual(model["G"], 8)
self.assertEqual(model["B"], 8)
self.assertEqual(model["N"], 1)
self.assertEqual(model["P"], True)
self.assertEqual(model["E"], 200)
self.assertEqual(model["W"], 1000)
def test_serialization_to_json(self):
json = TEST_CAM_PARAMS.model_dump_json()
self.assertEqual(json, EXPECTED_JSON)
print(json)
def test_deserialization(self):
model_deserialized = CameraParameters.model_validate_json(EXPECTED_JSON)
self.assertEqual(TEST_CAM_PARAMS, model_deserialized)

View File

@ -1,20 +0,0 @@
import struct
from opssat_tmtc.camera_params import CameraParameters
from opssat_tmtc.common import make_unique_id, EXPERIMENT_APID
def test_serde_serialization():
# Example serializatn
data = bytearray(make_unique_id(EXPERIMENT_APID))
params = CameraParameters(8, 8, 8, 1, True, 200, 1000)
serialized = params.to_json().encode("utf-8")
byte_string = bytearray(struct.pack("!{}s".format(len(serialized)), serialized))
print(byte_string)
print(params.serialize_for_uplink())
data.extend(params.serialize_for_uplink())
print(data)
# Example deserialization
data = '{"R": 100, "G": 150, "B": 200, "N": 3, "P": true, "E": 10, "W": 20}'
deserialized_params = CameraParameters.from_json(data)
print(deserialized_params)

View File

@ -1,4 +1,3 @@
use crate::pus::action::send_data_reply;
/// Device handler implementation for the IMS-100 Imager used on the OPS-SAT mission. /// Device handler implementation for the IMS-100 Imager used on the OPS-SAT mission.
/// ///
/// from the [OPSSAT Experimenter Wiki](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Camera_Introduction): /// from the [OPSSAT Experimenter Wiki](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Camera_Introduction):
@ -25,9 +24,11 @@ use crate::pus::action::send_data_reply;
/// v Y /// v Y
/// ///
/// see also https://opssat1.esoc.esa.int/dmsf/files/6/view /// see also https://opssat1.esoc.esa.int/dmsf/files/6/view
use crate::pus::action::send_data_reply;
use crate::requests::CompositeRequest; use crate::requests::CompositeRequest;
use derive_new::new; use derive_new::new;
use log::{debug, info}; use log::{debug, info};
use num_enum::TryFromPrimitive;
use ops_sat_rs::TimeStampHelper; use ops_sat_rs::TimeStampHelper;
use satrs::action::{ActionRequest, ActionRequestVariant}; use satrs::action::{ActionRequest, ActionRequestVariant};
use satrs::hk::HkRequest; use satrs::hk::HkRequest;
@ -88,8 +89,9 @@ const BALANCED_SINGLE_FLATSAT_CAM_PARAMS: CameraPictureParameters = CameraPictur
// TODO ls -l via cfdp // TODO ls -l via cfdp
// TODO howto downlink // TODO howto downlink
#[derive(Debug)] #[derive(Debug, TryFromPrimitive)]
pub enum CameraActionId { #[repr(u32)]
pub enum ActionId {
DefaultSingle = 1, DefaultSingle = 1,
BalancedSingle = 2, BalancedSingle = 2,
DefaultSingleFlatSat = 3, DefaultSingleFlatSat = 3,
@ -97,31 +99,6 @@ pub enum CameraActionId {
CustomParameters = 5, CustomParameters = 5,
} }
impl TryFrom<u32> for CameraActionId {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
value if value == CameraActionId::DefaultSingle as u32 => {
Ok(CameraActionId::DefaultSingle)
}
value if value == CameraActionId::BalancedSingle as u32 => {
Ok(CameraActionId::BalancedSingle)
}
value if value == CameraActionId::DefaultSingleFlatSat as u32 => {
Ok(CameraActionId::DefaultSingleFlatSat)
}
value if value == CameraActionId::BalancedSingleFlatSat as u32 => {
Ok(CameraActionId::BalancedSingleFlatSat)
}
value if value == CameraActionId::CustomParameters as u32 => {
Ok(CameraActionId::CustomParameters)
}
_ => Err(()),
}
}
}
// TODO what happens if limits are exceded // TODO what happens if limits are exceded
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, new)] #[derive(Debug, Serialize, Deserialize, new)]
@ -189,11 +166,9 @@ impl fmt::Display for CameraError {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub struct IMS100BatchHandler { pub struct Ims100BatchHandler {
id: UniqueApidTargetId, id: UniqueApidTargetId,
// mode_interface: MpscModeLeafInterface,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>, composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
// hk_reply_sender: mpsc::Sender<GenericMessage<HkReply>>,
tm_tx: mpsc::Sender<PacketAsVec>, tm_tx: mpsc::Sender<PacketAsVec>,
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>, action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
stamp_helper: TimeStampHelper, stamp_helper: TimeStampHelper,
@ -201,7 +176,7 @@ pub struct IMS100BatchHandler {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(dead_code)] #[allow(dead_code)]
impl IMS100BatchHandler { impl Ims100BatchHandler {
pub fn new( pub fn new(
id: UniqueApidTargetId, id: UniqueApidTargetId,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>, composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
@ -222,7 +197,6 @@ impl IMS100BatchHandler {
self.stamp_helper.update_from_now(); self.stamp_helper.update_from_now();
// Handle requests. // Handle requests.
self.handle_composite_requests(); self.handle_composite_requests();
// self.handle_mode_requests();
} }
pub fn handle_composite_requests(&mut self) { pub fn handle_composite_requests(&mut self) {
@ -264,13 +238,12 @@ impl IMS100BatchHandler {
requestor_info: &MessageMetadata, requestor_info: &MessageMetadata,
action_request: &ActionRequest, action_request: &ActionRequest,
) -> Result<(), CameraError> { ) -> Result<(), CameraError> {
let param = let param = match ActionId::try_from(action_request.action_id).expect("Invalid action id") {
match CameraActionId::try_from(action_request.action_id).expect("Invalid action id") { ActionId::DefaultSingle => DEFAULT_SINGLE_CAM_PARAMS,
CameraActionId::DefaultSingle => DEFAULT_SINGLE_CAM_PARAMS, ActionId::BalancedSingle => BALANCED_SINGLE_CAM_PARAMS,
CameraActionId::BalancedSingle => BALANCED_SINGLE_CAM_PARAMS, ActionId::DefaultSingleFlatSat => DEFAULT_SINGLE_FLATSAT_CAM_PARAMS,
CameraActionId::DefaultSingleFlatSat => DEFAULT_SINGLE_FLATSAT_CAM_PARAMS, ActionId::BalancedSingleFlatSat => BALANCED_SINGLE_FLATSAT_CAM_PARAMS,
CameraActionId::BalancedSingleFlatSat => BALANCED_SINGLE_FLATSAT_CAM_PARAMS, ActionId::CustomParameters => match &action_request.variant {
CameraActionId::CustomParameters => match &action_request.variant {
ActionRequestVariant::NoData => return Err(CameraError::NoDataSent), ActionRequestVariant::NoData => return Err(CameraError::NoDataSent),
ActionRequestVariant::StoreData(_) => { ActionRequestVariant::StoreData(_) => {
// let param = serde_json::from_slice() // let param = serde_json::from_slice()
@ -388,8 +361,7 @@ impl IMS100BatchHandler {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::handlers::camera::{ use crate::handlers::camera::{
CameraActionId, CameraPictureParameters, IMS100BatchHandler, ActionId, CameraPictureParameters, Ims100BatchHandler, DEFAULT_SINGLE_FLATSAT_CAM_PARAMS,
DEFAULT_SINGLE_FLATSAT_CAM_PARAMS,
}; };
use crate::requests::CompositeRequest; use crate::requests::CompositeRequest;
use ops_sat_rs::config::components::CAMERA_HANDLER; use ops_sat_rs::config::components::CAMERA_HANDLER;
@ -399,32 +371,47 @@ mod tests {
use satrs::request::{GenericMessage, MessageMetadata}; use satrs::request::{GenericMessage, MessageMetadata};
use satrs::tmtc::PacketAsVec; use satrs::tmtc::PacketAsVec;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
fn create_handler() -> ( struct Ims1000Testbench {
IMS100BatchHandler, pub handler: Ims100BatchHandler,
Sender<GenericMessage<CompositeRequest>>, pub composite_req_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
Receiver<PacketAsVec>, pub tm_receiver: mpsc::Receiver<PacketAsVec>,
Receiver<GenericMessage<ActionReplyPus>>, pub action_reply_rx: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
) { }
impl Default for Ims1000Testbench {
fn default() -> Self {
let (composite_request_tx, composite_request_rx) = mpsc::channel(); let (composite_request_tx, composite_request_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel(); let (tm_tx, tm_rx) = mpsc::channel();
let (action_reply_tx, action_reply_rx) = mpsc::channel(); let (action_reply_tx, action_reply_rx) = mpsc::channel();
let time_helper = TimeStampHelper::default(); let time_helper = TimeStampHelper::default();
let cam_handler: IMS100BatchHandler = IMS100BatchHandler::new( let cam_handler: Ims100BatchHandler = Ims100BatchHandler::new(
CAMERA_HANDLER, CAMERA_HANDLER,
composite_request_rx, composite_request_rx,
tm_tx, tm_tx,
action_reply_tx, action_reply_tx,
time_helper, time_helper,
); );
(cam_handler, composite_request_tx, tm_rx, action_reply_rx) Ims1000Testbench {
handler: Ims100BatchHandler::new(
CAMERA_HANDLER,
composite_request_rx,
tm_tx,
action_reply_tx,
time_helper,
),
composite_req_tx: composite_request_tx,
tm_receiver: tm_rx,
action_reply_rx,
}
}
} }
#[test] #[test]
fn command_line_execution() { fn command_line_execution() {
let (mut cam_handler, req_tx, tm_rx, action_reply_rx) = create_handler(); let mut testbench = Ims1000Testbench::default();
cam_handler testbench
.handler
.take_picture(DEFAULT_SINGLE_FLATSAT_CAM_PARAMS) .take_picture(DEFAULT_SINGLE_FLATSAT_CAM_PARAMS)
.unwrap(); .unwrap();
} }
@ -439,33 +426,34 @@ mod tests {
#[test] #[test]
fn test_action_req() { fn test_action_req() {
let (mut cam_handler, req_tx, tm_rx, action_reply_rx) = create_handler(); let mut testbench = Ims1000Testbench::default();
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap(); let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
let req = ActionRequest::new( let req = ActionRequest::new(
CameraActionId::CustomParameters as u32, ActionId::CustomParameters as u32,
ActionRequestVariant::VecData(data.as_bytes().to_vec()), ActionRequestVariant::VecData(data.as_bytes().to_vec()),
); );
cam_handler testbench
.handler
.handle_action_request(&MessageMetadata::new(1, 1), &req) .handle_action_request(&MessageMetadata::new(1, 1), &req)
.unwrap(); .unwrap();
} }
#[test] #[test]
fn test_action_req_channel() { fn test_action_req_channel() {
let (mut cam_handler, req_tx, tm_rx, action_reply_rx) = create_handler(); let mut testbench = Ims1000Testbench::default();
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap(); let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
let req = ActionRequest::new( let req = ActionRequest::new(
CameraActionId::CustomParameters as u32, ActionId::CustomParameters as u32,
ActionRequestVariant::VecData(data.as_bytes().to_vec()), ActionRequestVariant::VecData(data.as_bytes().to_vec()),
); );
let req = CompositeRequest::Action(req); let req = CompositeRequest::Action(req);
req_tx testbench
.composite_req_tx
.send(GenericMessage::new(MessageMetadata::new(1, 1), req)) .send(GenericMessage::new(MessageMetadata::new(1, 1), req))
.unwrap(); .unwrap();
cam_handler.periodic_operation(); testbench.handler.periodic_operation();
} }
} }

View File

@ -33,7 +33,7 @@ use crate::{
PusTcDistributor, PusTcMpscRouter, PusTcDistributor, PusTcMpscRouter,
}, },
}; };
use crate::{handlers::camera::IMS100BatchHandler, pus::event::create_event_service}; use crate::{handlers::camera::Ims100BatchHandler, pus::event::create_event_service};
use crate::{ use crate::{
interface::tcp_server::{SyncTcpTmSource, TcpTask}, interface::tcp_server::{SyncTcpTmSource, TcpTask},
interface::udp_server::{DynamicUdpTmHandler, UdpTmtcServer}, interface::udp_server::{DynamicUdpTmHandler, UdpTmtcServer},
@ -211,7 +211,7 @@ fn main() {
.expect("creating TCP SPP client failed"); .expect("creating TCP SPP client failed");
let timestamp_helper = TimeStampHelper::default(); let timestamp_helper = TimeStampHelper::default();
let mut camera_handler: IMS100BatchHandler = IMS100BatchHandler::new( let mut camera_handler: Ims100BatchHandler = Ims100BatchHandler::new(
CAMERA_HANDLER, CAMERA_HANDLER,
camera_composite_rx, camera_composite_rx,
tm_funnel_tx.clone(), tm_funnel_tx.clone(),