add granular event management control

This commit is contained in:
Robin Mueller
2026-09-23 15:31:35 +02:00
parent 883998e31d
commit 919f1aa65d
11 changed files with 500 additions and 26 deletions
+94 -1
View File
@@ -34,6 +34,58 @@ enum Commands {
Mgm1(MgmArgs),
MgmAssy(MgmAssemblyArgs),
AcsSubsystem(SubsystemArgs),
EventManager(EventManagerArgs),
}
#[derive(clap::Parser)]
struct EventManagerArgs {
#[command(subcommand)]
action: EventFilterAction,
}
#[derive(clap::Subcommand)]
enum EventFilterAction {
/// Enable event TM generation.
Enable(EventFilterArgs),
/// Disable event TM generation.
Disable(EventFilterArgs),
}
#[derive(clap::Args)]
struct EventFilterArgs {
#[arg(value_enum)]
component: EventSenderSelect,
/// Raw event ID. Without it, the filter applies to all events of the component.
#[arg(short, long)]
event_id: Option<u16>,
}
/// Components which emit events.
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
enum EventSenderSelect {
Controller,
Mgm0,
Mgm1,
MgmAssy,
Pcdu,
UdpServer,
TcpServer,
Ground,
}
impl From<EventSenderSelect> for types::ComponentId {
fn from(sender: EventSenderSelect) -> Self {
match sender {
EventSenderSelect::Controller => types::ComponentId::Controller,
EventSenderSelect::Mgm0 => types::ComponentId::AcsMgm0,
EventSenderSelect::Mgm1 => types::ComponentId::AcsMgm1,
EventSenderSelect::MgmAssy => types::ComponentId::AcsMgmAssembly,
EventSenderSelect::Pcdu => types::ComponentId::EpsPcdu,
EventSenderSelect::UdpServer => types::ComponentId::UdpServer,
EventSenderSelect::TcpServer => types::ComponentId::TcpServer,
EventSenderSelect::Ground => types::ComponentId::Ground,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, clap::ValueEnum)]
@@ -216,6 +268,39 @@ fn handle_mgm_command(
Ok(())
}
fn handle_event_manager_command(client: &UdpSocket, addr: SocketAddr, args: EventManagerArgs) {
use types::event_manager::request::Request;
let request = match args.action {
EventFilterAction::Enable(filter) => match filter.event_id {
Some(event_id) => Request::EnableEvent {
sender_id: filter.component.into(),
event_id,
},
None => Request::EnableComponent(filter.component.into()),
},
EventFilterAction::Disable(filter) => match filter.event_id {
Some(event_id) => Request::DisableEvent {
sender_id: filter.component.into(),
event_id,
},
None => Request::DisableComponent(filter.component.into()),
},
};
let request_packet = types::ccsds::CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(types::ComponentId::EventManager, MessageType::Event),
request,
);
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request_packet.sp_header);
log::info!(
"sending event manager request {:?} with TC ID {:#010x}",
request,
sent_tc_id.raw()
);
client.send_to(&request_packet.to_vec(), addr).unwrap();
}
fn setup_logger(level: log::LevelFilter) -> Result<(), fern::InitError> {
fern::Dispatch::new()
.format(|out, message, record| {
@@ -366,6 +451,7 @@ fn main() -> anyhow::Result<()> {
client.send_to(&request_packet, addr).unwrap();
}
}
Commands::EventManager(args) => handle_event_manager_command(&client, addr, args),
}
}
@@ -508,7 +594,14 @@ fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> {
types::ComponentId::UdpServer => todo!(),
types::ComponentId::TcpServer => todo!(),
types::ComponentId::Ground => todo!(),
types::ComponentId::EventManager => {}
types::ComponentId::EventManager => {
let response =
postcard::from_bytes::<types::event_manager::response::Response>(remainder);
log::info!(
"Received response from event manager: {:?}",
response.unwrap()
);
}
types::ComponentId::AcsController => todo!(),
types::ComponentId::AcsMgt => todo!(),
}
+5 -1
View File
@@ -46,7 +46,11 @@ impl Controller {
control::request::Request::Ping => self
.send_telemetry(Some(tc_id), control::response::Response::Ok),
control::request::Request::TestEvent => {
self.event_ctrl_tx.send(control::Event::TestEvent).unwrap()
self.event_ctrl_tx.send(control::Event::TestEvent).unwrap();
self.send_telemetry(
Some(tc_id),
control::response::Response::Ok,
);
}
}
}
+282 -9
View File
@@ -1,15 +1,19 @@
use std::collections::HashSet;
use satrs::spacepackets::CcsdsPacketIdAndPsc;
use types::{
ComponentId, Event, Message,
ComponentId, Event, EventId, Message,
acs::{mgm, mgm_assembly},
ccsds::CcsdsTmPacketOwned,
control, pcdu, tmtc,
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
control,
event_manager::{request::Request, response::Response},
pcdu, tmtc,
};
use crate::ccsds::pack_ccsds_tm_packet_for_now;
// TODO: We should add the capability to enable/disable the TM generation of individual events and
// event groups as well.
pub struct EventManager {
pub tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
pub ctrl_rx: std::sync::mpsc::Receiver<control::Event>,
/// Shared by all MGM instances, which is why the sender ID is part of the message.
pub mgm_rx: std::sync::mpsc::Receiver<(ComponentId, mgm::Event)>,
@@ -18,10 +22,62 @@ pub struct EventManager {
/// Shared by all TC sources, which is why the sender ID is part of the message.
pub tc_source_rx: std::sync::mpsc::Receiver<(ComponentId, tmtc::Event)>,
pub tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
/// Senders with all their event TM generation disabled.
disabled_components: HashSet<ComponentId>,
/// Individual (sender, event ID) pairs with their TM generation disabled.
disabled_events: HashSet<(ComponentId, u16)>,
}
impl EventManager {
pub fn new(
tc_rx: std::sync::mpsc::Receiver<CcsdsTcPacketOwned>,
ctrl_rx: std::sync::mpsc::Receiver<control::Event>,
mgm_rx: std::sync::mpsc::Receiver<(ComponentId, mgm::Event)>,
mgm_assembly_rx: std::sync::mpsc::Receiver<mgm_assembly::Event>,
pcdu_rx: std::sync::mpsc::Receiver<pcdu::Event>,
tc_source_rx: std::sync::mpsc::Receiver<(ComponentId, tmtc::Event)>,
tm_tx: std::sync::mpsc::SyncSender<CcsdsTmPacketOwned>,
) -> Self {
Self {
tc_rx,
ctrl_rx,
mgm_rx,
mgm_assembly_rx,
pcdu_rx,
tc_source_rx,
tm_tx,
disabled_components: HashSet::new(),
disabled_events: HashSet::new(),
}
}
/// Silences all event TM generation for `sender_id`, regardless of the specific event.
fn disable_component(&mut self, sender_id: ComponentId) {
self.disabled_components.insert(sender_id);
}
fn enable_component(&mut self, sender_id: ComponentId) {
self.disabled_components.remove(&sender_id);
}
/// Silences TM generation for one specific event of `sender_id`, leaving its other events
/// unaffected.
fn disable_event(&mut self, sender_id: ComponentId, event_id: u16) {
self.disabled_events.insert((sender_id, event_id));
}
fn enable_event(&mut self, sender_id: ComponentId, event_id: u16) {
self.disabled_events.remove(&(sender_id, event_id));
}
fn tm_generation_enabled(&self, sender_id: ComponentId, event_id: u16) -> bool {
!self.disabled_components.contains(&sender_id)
&& !self.disabled_events.contains(&(sender_id, event_id))
}
pub fn periodic_operation(&mut self) {
// Telecommands first, so filter changes already apply to the events of this cycle.
self.handle_telecommands();
while let Ok(event) = self.ctrl_rx.try_recv() {
self.event_to_tm(ComponentId::Controller, &Event::ControllerEvent(event));
}
@@ -39,20 +95,237 @@ impl EventManager {
}
}
fn handle_telecommands(&mut self) {
while let Ok(packet) = self.tc_rx.try_recv() {
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
let request = match postcard::from_bytes::<Request>(&packet.payload) {
Ok(request) => request,
Err(e) => {
log::warn!("failed to deserialize event manager request: {}", e);
continue;
}
};
log::info!(
"received request {:?} with TC ID {:#010x}",
request,
tc_id.raw()
);
match request {
Request::EnableComponent(sender_id) => self.enable_component(sender_id),
Request::DisableComponent(sender_id) => self.disable_component(sender_id),
Request::EnableEvent {
sender_id,
event_id,
} => self.enable_event(sender_id, event_id),
Request::DisableEvent {
sender_id,
event_id,
} => self.disable_event(sender_id, event_id),
}
self.send_tm(ComponentId::EventManager, Some(tc_id), &Response::Ok);
}
}
pub fn event_to_tm(
&mut self,
sender_id: ComponentId,
event: &(impl serde::Serialize + Message),
event: &(impl serde::Serialize + Message + EventId + core::fmt::Debug),
) {
match pack_ccsds_tm_packet_for_now(sender_id, None, event) {
let tm_enabled = self.tm_generation_enabled(sender_id, event.event_id());
log::info!(
"event {:?} (ID {}) from {:?}{}",
event,
event.event_id(),
sender_id,
if tm_enabled { "" } else { ", TM disabled" }
);
if !tm_enabled {
return;
}
self.send_tm(sender_id, None, event);
}
fn send_tm(
&self,
sender_id: ComponentId,
tc_id: Option<CcsdsPacketIdAndPsc>,
payload: &(impl serde::Serialize + Message),
) {
match pack_ccsds_tm_packet_for_now(sender_id, tc_id, payload) {
Ok(packet) => {
if let Err(e) = self.tm_tx.send(packet) {
log::warn!("error sending event TM packet: {:?}", e);
log::warn!("error sending TM packet: {:?}", e);
}
}
Err(e) => {
log::warn!("error packing event TM packet: {:?}", e);
log::warn!("error packing TM packet: {:?}", e);
}
}
}
}
#[cfg(test)]
mod tests {
use std::sync::mpsc::{self, TryRecvError};
use arbitrary_int::u11;
use satrs::spacepackets::SpacePacketHeader;
use types::{Apid, MessageType, TcHeader, acs::mgm};
use super::*;
struct Testbench {
tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
mgm_event_tx: mpsc::SyncSender<(ComponentId, mgm::Event)>,
tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
event_manager: EventManager,
}
impl Testbench {
fn new() -> Self {
let (tc_tx, tc_rx) = mpsc::sync_channel(5);
let (_ctrl_tx, ctrl_rx) = mpsc::sync_channel(5);
let (mgm_event_tx, mgm_rx) = mpsc::sync_channel(5);
let (_mgm_assembly_tx, mgm_assembly_rx) = mpsc::sync_channel(5);
let (_pcdu_tx, pcdu_rx) = mpsc::sync_channel(5);
let (_tc_source_tx, tc_source_rx) = mpsc::sync_channel(5);
let (tm_tx, tm_rx) = mpsc::sync_channel(5);
Self {
tc_tx,
mgm_event_tx,
tm_rx,
event_manager: EventManager::new(
tc_rx,
ctrl_rx,
mgm_rx,
mgm_assembly_rx,
pcdu_rx,
tc_source_rx,
tm_tx,
),
}
}
fn send_request(&self, request: Request) {
self.tc_tx
.send(CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(ComponentId::EventManager, MessageType::Event),
request,
))
.unwrap();
}
}
#[test]
fn test_events_enabled_by_default() {
let mut tb = Testbench::new();
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected event TM");
}
#[test]
fn test_disabled_component_silences_all_its_events() {
let mut tb = Testbench::new();
tb.event_manager.disable_component(ComponentId::AcsMgm0);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.mgm_event_tx
.send((
ComponentId::AcsMgm0,
mgm::Event::ModeChanged(types::DeviceMode::Normal),
))
.unwrap();
tb.event_manager.periodic_operation();
assert!(matches!(tb.tm_rx.try_recv(), Err(TryRecvError::Empty)));
}
#[test]
fn test_disabled_component_does_not_affect_other_components() {
let mut tb = Testbench::new();
tb.event_manager.disable_component(ComponentId::AcsMgm1);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected event TM");
}
#[test]
fn test_disabled_event_only_silences_that_specific_event() {
let mut tb = Testbench::new();
tb.event_manager.disable_event(
ComponentId::AcsMgm0,
mgm::Event::SpiFaultThresholdExceeded.event_id(),
);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.mgm_event_tx
.send((
ComponentId::AcsMgm0,
mgm::Event::ModeChanged(types::DeviceMode::Normal),
))
.unwrap();
tb.event_manager.periodic_operation();
let tm = tb.tm_rx.try_recv().expect("expected event TM");
let event: mgm::Event = postcard::from_bytes(&tm.payload).unwrap();
assert!(matches!(event, mgm::Event::ModeChanged(_)));
assert!(matches!(tb.tm_rx.try_recv(), Err(TryRecvError::Empty)));
}
#[test]
fn test_re_enabling_a_component_restores_its_events() {
let mut tb = Testbench::new();
tb.event_manager.disable_component(ComponentId::AcsMgm0);
tb.event_manager.enable_component(ComponentId::AcsMgm0);
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected event TM");
}
#[test]
fn test_disable_component_request() {
let mut tb = Testbench::new();
tb.send_request(Request::DisableComponent(ComponentId::AcsMgm0));
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
let tm = tb.tm_rx.try_recv().expect("expected request response TM");
assert_eq!(tm.tm_header.sender_id, ComponentId::EventManager);
assert!(tm.tm_header.tc_id.is_some());
let response: Response = postcard::from_bytes(&tm.payload).unwrap();
assert_eq!(response, Response::Ok);
assert!(matches!(tb.tm_rx.try_recv(), Err(TryRecvError::Empty)));
}
#[test]
fn test_disable_event_request() {
let mut tb = Testbench::new();
tb.send_request(Request::DisableEvent {
sender_id: ComponentId::AcsMgm0,
event_id: mgm::Event::SpiFaultThresholdExceeded.event_id(),
});
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected request response TM");
tb.send_request(Request::EnableEvent {
sender_id: ComponentId::AcsMgm0,
event_id: mgm::Event::SpiFaultThresholdExceeded.event_id(),
});
tb.mgm_event_tx
.send((ComponentId::AcsMgm0, mgm::Event::SpiFaultThresholdExceeded))
.unwrap();
tb.event_manager.periodic_operation();
tb.tm_rx.try_recv().expect("expected request response TM");
tb.tm_rx.try_recv().expect("expected event TM");
}
}
+11 -8
View File
@@ -81,6 +81,7 @@ fn main() {
let (acs_subsystem_tc_tx, acs_subsystem_tc_rx) = mpsc::sync_channel(10);
let (pcdu_handler_tc_tx, pcdu_handler_tc_rx) = mpsc::sync_channel(30);
let (controller_tc_tx, controller_tc_rx) = mpsc::sync_channel(10);
let (event_manager_tc_tx, event_manager_tc_rx) = mpsc::sync_channel(10);
let (mgt_request_tx, mgt_request_rx) = mpsc::sync_channel(5);
let (mgt_report_tx, mgt_report_rx) = mpsc::sync_channel(5);
@@ -105,14 +106,15 @@ fn main() {
let (mgm_assembly_event_tx, mgm_assembly_event_rx) = mpsc::sync_channel(10);
let (pcdu_event_tx, pcdu_event_rx) = mpsc::sync_channel(10);
let (tc_source_event_tx, tc_source_event_rx) = mpsc::sync_channel(10);
let mut event_manager = EventManager {
ctrl_rx: event_ctrl_rx,
mgm_rx: mgm_event_rx,
mgm_assembly_rx: mgm_assembly_event_rx,
pcdu_rx: pcdu_event_rx,
tc_source_rx: tc_source_event_rx,
tm_tx: tm_sink_tx.clone(),
};
let mut event_manager = EventManager::new(
event_manager_tc_rx,
event_ctrl_rx,
mgm_event_rx,
mgm_assembly_event_rx,
pcdu_event_rx,
tc_source_event_rx,
tm_sink_tx.clone(),
);
let mut controller = Controller::new(controller_tc_rx, tm_sink_tx.clone(), event_ctrl_tx);
@@ -124,6 +126,7 @@ fn main() {
tc_source.add_target(ComponentId::AcsMgm1, mgm_1_handler_tc_tx);
tc_source.add_target(ComponentId::AcsMgmAssembly, mgm_assembly_tc_tx);
tc_source.add_target(ComponentId::AcsSubsystem, acs_subsystem_tc_tx);
tc_source.add_target(ComponentId::EventManager, event_manager_tc_tx);
let tc_sender = TmTcSender::Normal(tc_source_tx.clone());
let udp_tm_handler = UdpTmHandlerWithChannel {
+9 -1
View File
@@ -58,7 +58,9 @@ pub struct SensorData {
pub z: f32,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// The SPI fault counter exceeded its threshold, the component was marked faulty and
/// commanded off.
@@ -73,6 +75,12 @@ impl crate::Message for Event {
}
}
impl crate::EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
pub mod response {
use crate::{DeviceMode, Message, acs::mgm::SensorData};
+9 -1
View File
@@ -137,7 +137,9 @@ pub mod response {
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// A commanded or autonomous mode transition completed.
ModeChanged(Mode),
@@ -148,3 +150,9 @@ impl crate::Message for Event {
crate::MessageType::Event
}
}
impl crate::EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
+20 -2
View File
@@ -1,6 +1,8 @@
use crate::Message;
use crate::{EventId, Message};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
TestEvent,
}
@@ -11,6 +13,12 @@ impl Message for Event {
}
}
impl EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
pub mod request {
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
@@ -37,3 +45,13 @@ pub mod response {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_id() {
assert_eq!(Event::TestEvent.event_id(), 0);
}
}
+34
View File
@@ -0,0 +1,34 @@
pub mod request {
use crate::ComponentId;
/// Controls which events are converted to TM. Component and event filters are independent:
/// re-enabling a component does not re-enable its individually disabled events.
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Request {
EnableComponent(ComponentId),
DisableComponent(ComponentId),
EnableEvent {
sender_id: ComponentId,
event_id: u16,
},
DisableEvent {
sender_id: ComponentId,
event_id: u16,
},
}
}
pub mod response {
use crate::Message;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Response {
Ok,
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Verification
}
}
}
+17
View File
@@ -9,6 +9,7 @@ use spacepackets::{
pub mod acs;
pub mod ccsds;
pub mod control;
pub mod event_manager;
pub mod pcdu;
pub mod tmtc;
@@ -66,6 +67,14 @@ impl Message for Event {
}
}
impl EventId for Event {
fn event_id(&self) -> u16 {
match self {
Event::ControllerEvent(event) => event.event_id(),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct TmHeader {
@@ -158,6 +167,14 @@ pub trait Message {
fn message_type(&self) -> MessageType;
}
/// Stable, per-variant numeric identifier for an event, meant to be referenced from ground
/// (e.g. to enable/disable TM generation for one specific event) and to stay the same across
/// releases. Unlike [core::mem::discriminant], this can be constructed from a raw number
/// received in a telecommand.
pub trait EventId {
fn event_id(&self) -> u16;
}
/// Generic device mode which covers the requirements of most devices.
///
/// The states are related both to the physical and the logical state of the device. Some
+9 -1
View File
@@ -56,7 +56,9 @@ pub enum SwitchStateBinary {
On = 1,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// Sending a request to the PCDU over the serial/simulator link failed.
SerialCommError,
@@ -68,6 +70,12 @@ impl crate::Message for Event {
}
}
impl crate::EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
pub type SwitchMapBinary = HashMap<SwitchId, SwitchStateBinary>;
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
+10 -2
View File
@@ -1,6 +1,8 @@
use crate::{ComponentId, Message};
use crate::{ComponentId, EventId, Message};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// A received CCSDS packet failed CRC or basic format validation.
InvalidTcPacket,
@@ -15,3 +17,9 @@ impl Message for Event {
crate::MessageType::Event
}
}
impl EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}