dcs2 0.1.0

An extensible distributed control system framework made in rust with no-std support.
Documentation
//! # DCS Nodes module
//! Nodes are either sensor nodes that read data and coordinate it, and actuator nodes that
//! receive commands from sensor nodes.
pub mod actuator;
pub mod sensor;
pub mod translator;
pub mod utils;

use alloc::format;
use core::fmt::{Debug, Display, Formatter};
use core::hash::{Hash, Hasher};
use heapless::{LinearMap, String, Vec};
use serde::{Deserialize, Serialize};

use crate::communication::connection::{Address, Connection, ConnectionFactory};
use crate::communication::messages::{IdentificableMessage, SimpleCodifier, Messages, PackageBuilder};
use crate::communication::router::{Router, UNKNOWN_ROUTE_STARTING_ID};
use crate::coordination::{CoordinationService, Stopwatch};
use crate::membership::client::{MembersAddresses, MembershipMessage};
use crate::membership::metadata::{BasicMetadata, CommunicationMetadata, CoordinationMetadataBuilder, SerializableMetadata};
use crate::properties::{CLUSTER_NODE_COUNT, CLUSTERS_PER_TRANSLATOR};

use crate::rules::measurements::ClusterType;

/// The nodes of a system are either sensors or actuators.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum NodeRole {
    ACTUATOR,
    SENSOR(ClusterType),
    TRANSLATOR,
}

/// Universal id used throughout the system
#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Serialize, Deserialize, Hash, )]
pub struct SystemNodeId {
    id: u32,
}

impl PartialEq<u32> for SystemNodeId {
    fn eq(&self, other: &u32) -> bool {
        self.id == *other
    }
}

impl Display for SystemNodeId {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> core::fmt::Result {
        write!(formatter, "{}", self.id)
    }
}

impl SystemNodeId {
    /// Increments the current id and returns it.
    pub fn next(&self) -> Self {
        Self { id: self.id + 1 }
    }

    pub fn is_unknown(&self) -> bool { self.id >= UNKNOWN_ROUTE_STARTING_ID }
}

impl From<u32> for SystemNodeId {
    fn from(id: u32) -> Self {
        Self { id }
    }
}

impl Into<u32> for SystemNodeId {
    fn into(self) -> u32 {
        self.id
    }
}

#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Serialize, Deserialize)]
pub struct SystemClusterId {
    id: u32,
}

impl Hash for SystemClusterId {
    fn hash<H: Hasher>(&self, state: &mut H) {
        let bytes = self.id.to_le_bytes();
        state.write(&bytes)
    }
}

impl Display for SystemClusterId {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> core::fmt::Result {
        write!(formatter, "{}", self.id)
    }
}

impl From<u32> for SystemClusterId {
    fn from(id: u32) -> Self {
        Self { id }
    }
}

/// Metadata associated to the node at the system level.
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct SystemMetadata<CommMetadata: CommunicationMetadata> {
    pub id: SystemNodeId,
    pub role: NodeRole,
    pub clusters: Vec<SystemClusterId, CLUSTERS_PER_TRANSLATOR>,
    pub communication: CommMetadata,
}

impl<CommMetadata: CommunicationMetadata> SystemMetadata<CommMetadata> {
    pub fn same_cluster(&self, cluster_id: SystemClusterId) -> bool {
        self.clusters.contains(&cluster_id)
    }

    pub fn log_clusters(&self) -> String<50> {
        let mut clusters = String::<50>::new();
        for cluster_id in self.clusters.iter() {
            if !clusters.is_empty() {
                clusters.push_str(", ").unwrap();
            }
            clusters.push_str(format!("#{}", cluster_id).as_str()).unwrap();
        }
        clusters
    }
}

impl<CommMetadata: CommunicationMetadata + SerializableMetadata> Display
    for SystemMetadata<CommMetadata>
{
    fn fmt(&self, formatter: &mut Formatter<'_>) -> core::fmt::Result {
        let mut clusters = String::<50>::new();
        for cluster_id in self.clusters.iter() {
            if !clusters.is_empty() {
                clusters.push_str(", ").unwrap();
            }
            clusters.push_str(format!("#{}", cluster_id).as_str()).unwrap();
        }

        write!(
            formatter,
            "[ID: #{} | Role: {:?} | Clusters: [{}] | Communication: {}]",
            self.id, self.role, clusters, self.communication
        )
    }
}

impl<CommMetadata: CommunicationMetadata + SerializableMetadata> BasicMetadata
    for SystemMetadata<CommMetadata>
{
}

impl<CommMetadata: CommunicationMetadata + SerializableMetadata> SerializableMetadata
    for SystemMetadata<CommMetadata>
{
}

/// This sdk comrises of all the types that must be defined for the coordination of the system to work.
/// For example if a coordination algorithm such as paxos were to be implemented these interfaces
/// must be implemented as well.
pub trait CoordinationSDK {
    type Clock: Stopwatch + Default;
    /// Members wil hold the cluster's nodes and their metadata
    type Members: Clone + Debug + From<LinearMap<SystemNodeId, Self::Metadata, CLUSTER_NODE_COUNT>>;
    /// The message to be sent at the coordination level.
    type Message: IdentificableMessage;
    /// This metadata is associated to every node at the coordination level
    type Metadata: BasicMetadata + SerializableMetadata;
    /// The type of the [`crate::coordination::CoordinationService`] to be used.
    type Service: CoordinationService<Self::Clock, Self::Message, Self::Members, Self::Metadata>;
    /// This [`PackageBuilder`] will be used to construct the messages to be sent and received.
    type PackageBuilder: PackageBuilder<NodeId =SystemNodeId, Message = Self::Message> + Default;
    type MetadataBuilder: CoordinationMetadataBuilder<Self::Metadata>;
}

/// This sdk comrises of all the types that must be defined for the communication of the system to work.
/// For example if tcp/ip should be used instead of bluetooth these are the requirements that should
/// be fulfilled
pub trait CommunicationSDK {
    type Address: Address;
    type Connection: Connection;
    type Metadata: CommunicationMetadata<Addr = Self::Address>;
    type Router: Router<Address = Self::Address>;
    type Factory: ConnectionFactory<Address = Self::Address, Connection = Self::Connection>;
}

/// This sdk contains all the information to serialize all of the system's messages.
pub trait SerializationSDK<
    Addr: Address,
    CoordMessage: IdentificableMessage,
    CoordMetadata: BasicMetadata + SerializableMetadata,
    CommMetadata: CommunicationMetadata<Addr = Addr>,
>
{
    type GeneralMessageCodifier: SimpleCodifier<Data=Messages<CoordMessage>>;
    type MembershipMessageCodifier: SimpleCodifier<Data=MembershipMessage>;
    type AddressCodifier: SimpleCodifier<Data=MembersAddresses<Addr>>;
    type SystemMetadataCodifier: SimpleCodifier<Data=SystemMetadata<CommMetadata>>;
    type CoordinatedMetadataCodifier: SimpleCodifier<Data=CoordMetadata>;
}