Use pydantic #23
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||||
|
20
pytmtc/opssat_tmtc/camera.py
Normal file
20
pytmtc/opssat_tmtc/camera.py
Normal 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
|
@ -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")
|
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
0
pytmtc/tests/__init__.py
Normal file
27
pytmtc/tests/test_cam.py
Normal file
27
pytmtc/tests/test_cam.py
Normal 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)
|
@ -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)
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
Loading…
Reference in New Issue
Block a user