mod actor;
mod command_handler;
mod command_presets;
mod commands;
mod error;
mod model;
mod output;
mod point_value;
mod system_command_handler;
use std::{
collections::{HashMap, hash_map::Entry},
sync::Arc,
};
pub use command_handler::{
CommandContext, CommandHandling, RejectAllCommands, RtuCommandHandler, command_handler_from_fn,
};
pub use command_presets::MapCommandsToSameIoaMonitoring;
pub use error::{RtuHandleError, SetPointError};
pub use model::{PointAddress, PointValue, RtuInitialMaps, RtuInitialPoint};
use snafu::whatever;
pub use system_command_handler::{
CounterInterrogationContext, DefaultRtuClockSyncSystemHandler,
DefaultRtuCounterInterrogationHandler, DefaultRtuReadSystemHandler,
DefaultRtuResetProcessSystemHandler, DefaultRtuTestSystemHandler, RtuClockSyncSystemHandler,
RtuCounterInterrogationHandler, RtuReadSystemHandler, RtuResetProcessSystemHandler,
RtuSystemHandlers, RtuTestSystemHandler, SystemCommandContext,
};
use crate::{config::ServerConfig, error::Error};
fn build_rtu_initial_maps(
initial_points: impl IntoIterator<Item = impl Into<RtuInitialPoint>>,
) -> Result<RtuInitialMaps, Error> {
let mut points = HashMap::new();
let mut interrogation_groups = HashMap::new();
let mut counter_groups = HashMap::new();
for p in initial_points.into_iter().map(Into::into) {
let RtuInitialPoint { address, value, interrogation_group, counter_group } = p;
if let Some(g) = interrogation_group
&& !(1..=16).contains(&g)
{
whatever!(
"invalid interrogation group {g} for RTU point CA {} IOA {}",
address.common_address,
address.information_object_address
);
}
if let Some(cg) = counter_group {
if !(1..=4).contains(&cg) {
whatever!(
"invalid counter interrogation group {cg} for RTU point CA {} IOA {}",
address.common_address,
address.information_object_address
);
}
if !value.is_counter_integration() {
whatever!(
"counter interrogation group set for non M_IT point CA {} IOA {}",
address.common_address,
address.information_object_address
);
}
}
match points.entry(address) {
Entry::Vacant(e) => {
e.insert(value);
if let Some(g) = interrogation_group {
interrogation_groups.insert(address, g);
}
if let Some(cg) = counter_group {
counter_groups.insert(address, cg);
}
}
Entry::Occupied(_) => {
whatever!("duplicate RTU point address in start(): {address}");
}
}
}
Ok(RtuInitialMaps { points, interrogation_groups, counter_groups })
}
#[derive(Debug)]
pub struct RtuServer(());
impl RtuServer {
pub async fn start(
config: ServerConfig,
initial_points: impl IntoIterator<Item = impl Into<RtuInitialPoint>>,
command_handler: Arc<dyn RtuCommandHandler>,
) -> Result<RtuServerHandle, Error> {
Self::start_with_system_handlers(
config,
initial_points,
command_handler,
RtuSystemHandlers::default(),
)
.await
}
pub async fn start_with_system_handlers(
config: ServerConfig,
initial_points: impl IntoIterator<Item = impl Into<RtuInitialPoint>>,
command_handler: Arc<dyn RtuCommandHandler>,
system_handlers: RtuSystemHandlers,
) -> Result<RtuServerHandle, Error> {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let ingress = actor::NetworkIngress::new(tx.clone());
let server = crate::server::Server::start(config, ingress).await?;
let RtuInitialMaps { points, interrogation_groups, counter_groups } =
build_rtu_initial_maps(initial_points)?;
let server_for_actor = server.clone();
tokio::spawn(actor::run_actor(
rx,
server_for_actor,
points,
interrogation_groups,
counter_groups,
command_handler,
system_handlers,
));
Ok(RtuServerHandle { tx })
}
}
pub async fn start_rtu_server_ephemeral(
mut server_config: ServerConfig,
initial_points: impl IntoIterator<Item = impl Into<RtuInitialPoint>>,
command_handler: Arc<dyn RtuCommandHandler>,
system_handlers: RtuSystemHandlers,
) -> Result<(RtuServerHandle, std::net::SocketAddr), Error> {
server_config.address = "127.0.0.1".into();
server_config.port = 0;
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let ingress = actor::NetworkIngress::new(tx.clone());
let (server, listen_addr) =
crate::server::Server::start_with_listen_addr(server_config, ingress).await?;
let RtuInitialMaps { points, interrogation_groups, counter_groups } =
build_rtu_initial_maps(initial_points)?;
let server_for_actor = server.clone();
tokio::spawn(actor::run_actor(
rx,
server_for_actor,
points,
interrogation_groups,
counter_groups,
command_handler,
system_handlers,
));
Ok((RtuServerHandle { tx }, listen_addr))
}
#[derive(Clone)]
pub struct RtuServerHandle {
tx: tokio::sync::mpsc::UnboundedSender<actor::ActorMsg>,
}
impl std::fmt::Debug for RtuServerHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("RtuServerHandle")
}
}
impl RtuServerHandle {
pub async fn set_point(
&self,
address: PointAddress,
value: PointValue,
) -> Result<(), RtuHandleError> {
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
self.tx
.send(actor::ActorMsg::SetPoint { address, value, reply: reply_tx })
.map_err(|_| RtuHandleError::Disconnected)?;
match reply_rx.await {
Err(_) => Err(RtuHandleError::ActorStopped),
Ok(r) => r.map_err(Into::into),
}
}
pub async fn register_point(
&self,
address: PointAddress,
initial: PointValue,
) -> Result<(), RtuHandleError> {
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
self.tx
.send(actor::ActorMsg::Register {
address,
initial,
interrogation_group: None,
counter_group: None,
reply: reply_tx,
})
.map_err(|_| RtuHandleError::Disconnected)?;
finish_actor_register_reply(reply_rx).await
}
pub async fn register_point_with_interrogation_group(
&self,
address: PointAddress,
initial: PointValue,
interrogation_group: u8,
) -> Result<(), RtuHandleError> {
if !(1..=16).contains(&interrogation_group) {
return Err(RtuHandleError::InvalidInterrogationGroup { group: interrogation_group });
}
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
self.tx
.send(actor::ActorMsg::Register {
address,
initial,
interrogation_group: Some(interrogation_group),
counter_group: None,
reply: reply_tx,
})
.map_err(|_| RtuHandleError::Disconnected)?;
finish_actor_register_reply(reply_rx).await
}
pub async fn register_point_with_counter_interrogation_group(
&self,
address: PointAddress,
initial: PointValue,
counter_group: u8,
) -> Result<(), RtuHandleError> {
if !(1..=4).contains(&counter_group) {
return Err(RtuHandleError::InvalidCounterInterrogationGroup { group: counter_group });
}
if !initial.is_counter_integration() {
return Err(RtuHandleError::CounterGroupRequiresCounterPoint);
}
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
self.tx
.send(actor::ActorMsg::Register {
address,
initial,
interrogation_group: None,
counter_group: Some(counter_group),
reply: reply_tx,
})
.map_err(|_| RtuHandleError::Disconnected)?;
finish_actor_register_reply(reply_rx).await
}
pub async fn unregister_point(&self, address: PointAddress) -> Result<(), RtuHandleError> {
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
self.tx
.send(actor::ActorMsg::Unregister { address, reply: reply_tx })
.map_err(|_| RtuHandleError::Disconnected)?;
match reply_rx.await {
Err(_) => Err(RtuHandleError::ActorStopped),
Ok(Ok(())) => Ok(()),
Ok(Err(address)) => Err(RtuHandleError::NotRegistered { address }),
}
}
pub async fn register_points(
&self,
points: impl IntoIterator<Item = impl Into<RtuInitialPoint>>,
) -> Result<(), RtuHandleError> {
let points: Vec<RtuInitialPoint> = points.into_iter().map(Into::into).collect();
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
self.tx
.send(actor::ActorMsg::RegisterPoints { points, reply: reply_tx })
.map_err(|_| RtuHandleError::Disconnected)?;
finish_actor_register_reply(reply_rx).await
}
pub async fn unregister_all(&self) -> Result<usize, RtuHandleError> {
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
self.tx
.send(actor::ActorMsg::UnregisterAll { reply: reply_tx })
.map_err(|_| RtuHandleError::Disconnected)?;
match reply_rx.await {
Err(_) => Err(RtuHandleError::ActorStopped),
Ok(n) => Ok(n),
}
}
}
async fn finish_actor_register_reply<E>(
reply: tokio::sync::oneshot::Receiver<Result<(), E>>,
) -> Result<(), RtuHandleError>
where
E: Into<RtuHandleError>,
{
reply.await.map_err(|_| RtuHandleError::ActorStopped).and_then(|r| r.map_err(Into::into))
}
impl From<actor::RegisterPointError> for RtuHandleError {
fn from(e: actor::RegisterPointError) -> Self {
match e {
actor::RegisterPointError::AlreadyRegistered(address) => {
Self::AlreadyRegistered { address }
}
actor::RegisterPointError::InvalidInterrogationGroup { group } => {
Self::InvalidInterrogationGroup { group }
}
actor::RegisterPointError::InvalidCounterInterrogationGroup { group } => {
Self::InvalidCounterInterrogationGroup { group }
}
actor::RegisterPointError::CounterGroupRequiresCounterPoint => {
Self::CounterGroupRequiresCounterPoint
}
}
}
}
impl From<actor::RegisterPointsError> for RtuHandleError {
fn from(e: actor::RegisterPointsError) -> Self {
match e {
actor::RegisterPointsError::DuplicateInInput => Self::DuplicateAddressInInput,
actor::RegisterPointsError::AlreadyInModel { address } => {
Self::AlreadyRegistered { address }
}
actor::RegisterPointsError::InvalidInterrogationGroup { group } => {
Self::InvalidInterrogationGroup { group }
}
actor::RegisterPointsError::InvalidCounterInterrogationGroup { group } => {
Self::InvalidCounterInterrogationGroup { group }
}
actor::RegisterPointsError::CounterGroupRequiresCounterPoint => {
Self::CounterGroupRequiresCounterPoint
}
}
}
}