use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use crate::config::DeviceConfig;
use crate::device::BoxedDevice;
use crate::error::{Error, Result};
use crate::protocol::Protocol;
use crate::version::RELEASE_VERSION;
pub trait DeviceFactory: Send + Sync {
fn protocol(&self) -> Protocol;
fn create(&self, config: DeviceConfig) -> Result<BoxedDevice>;
fn create_batch(&self, configs: Vec<DeviceConfig>) -> Result<Vec<BoxedDevice>> {
configs.into_iter().map(|c| self.create(c)).collect()
}
fn validate(&self, config: &DeviceConfig) -> Result<()> {
if config.id.is_empty() {
return Err(Error::Config("Device ID cannot be empty".into()));
}
if config.name.is_empty() {
return Err(Error::Config("Device name cannot be empty".into()));
}
if config.protocol != self.protocol() {
return Err(Error::Config(format!(
"Protocol mismatch: expected {:?}, got {:?}",
self.protocol(),
config.protocol
)));
}
Ok(())
}
fn metadata(&self) -> FactoryMetadata {
FactoryMetadata {
protocol: self.protocol(),
version: RELEASE_VERSION.to_string(),
description: format!("{:?} device factory", self.protocol()),
capabilities: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct FactoryMetadata {
pub protocol: Protocol,
pub version: String,
pub description: String,
pub capabilities: Vec<String>,
}
pub type BoxedFactory = Box<dyn DeviceFactory>;
pub struct FactoryRegistry {
factories: RwLock<HashMap<Protocol, Arc<BoxedFactory>>>,
}
impl FactoryRegistry {
pub fn new() -> Self {
Self {
factories: RwLock::new(HashMap::new()),
}
}
pub fn register<F: DeviceFactory + 'static>(&self, factory: F) -> Result<()> {
let protocol = factory.protocol();
let mut factories = self.factories.write();
if factories.contains_key(&protocol) {
return Err(Error::Config(format!(
"Factory already registered for protocol {:?}",
protocol
)));
}
factories.insert(protocol, Arc::new(Box::new(factory)));
Ok(())
}
pub fn register_boxed(&self, factory: BoxedFactory) -> Result<()> {
let protocol = factory.protocol();
let mut factories = self.factories.write();
if factories.contains_key(&protocol) {
return Err(Error::Config(format!(
"Factory already registered for protocol {:?}",
protocol
)));
}
factories.insert(protocol, Arc::new(factory));
Ok(())
}
pub fn unregister(&self, protocol: Protocol) -> bool {
self.factories.write().remove(&protocol).is_some()
}
pub fn get(&self, protocol: Protocol) -> Option<Arc<BoxedFactory>> {
self.factories.read().get(&protocol).cloned()
}
pub fn has(&self, protocol: Protocol) -> bool {
self.factories.read().contains_key(&protocol)
}
pub fn protocols(&self) -> Vec<Protocol> {
self.factories.read().keys().copied().collect()
}
pub fn all_metadata(&self) -> Vec<FactoryMetadata> {
self.factories
.read()
.values()
.map(|f| f.metadata())
.collect()
}
pub fn create_device(&self, config: DeviceConfig) -> Result<BoxedDevice> {
let factory = self
.get(config.protocol)
.ok_or_else(|| Error::NotSupported(format!("Protocol {:?}", config.protocol)))?;
factory.validate(&config)?;
factory.create(config)
}
pub fn create_devices(&self, configs: Vec<DeviceConfig>) -> Result<Vec<BoxedDevice>> {
configs.into_iter().map(|c| self.create_device(c)).collect()
}
}
impl Default for FactoryRegistry {
fn default() -> Self {
Self::new()
}
}
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> &str {
RELEASE_VERSION
}
fn description(&self) -> &str {
""
}
fn initialize(&mut self) -> Result<()> {
Ok(())
}
fn register_factories(&self, _registry: &FactoryRegistry) -> Result<()> {
Ok(())
}
fn shutdown(&mut self) -> Result<()> {
Ok(())
}
}
pub type BoxedPlugin = Box<dyn Plugin>;
pub struct PluginManager {
plugins: RwLock<Vec<BoxedPlugin>>,
registry: Arc<FactoryRegistry>,
}
impl PluginManager {
pub fn new(registry: Arc<FactoryRegistry>) -> Self {
Self {
plugins: RwLock::new(Vec::new()),
registry,
}
}
pub fn load<P: Plugin + 'static>(&self, mut plugin: P) -> Result<()> {
plugin.initialize()?;
plugin.register_factories(&self.registry)?;
self.plugins.write().push(Box::new(plugin));
Ok(())
}
pub fn load_boxed(&self, mut plugin: BoxedPlugin) -> Result<()> {
plugin.initialize()?;
plugin.register_factories(&self.registry)?;
self.plugins.write().push(plugin);
Ok(())
}
pub fn plugin_count(&self) -> usize {
self.plugins.read().len()
}
pub fn plugin_info(&self) -> Vec<PluginInfo> {
self.plugins
.read()
.iter()
.map(|p| PluginInfo {
name: p.name().to_string(),
version: p.version().to_string(),
description: p.description().to_string(),
})
.collect()
}
pub fn registry(&self) -> &Arc<FactoryRegistry> {
&self.registry
}
pub fn shutdown_all(&self) -> Result<()> {
for plugin in self.plugins.write().iter_mut() {
plugin.shutdown()?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PluginInfo {
pub name: String,
pub version: String,
pub description: String,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::{Device, DeviceInfo, DeviceStatistics};
use crate::tags::Tags;
use crate::types::{DataPoint, DataPointDef, DataPointId};
use crate::value::Value;
use async_trait::async_trait;
struct MockDevice {
info: DeviceInfo,
}
impl MockDevice {
fn new(id: &str, name: &str) -> Self {
Self {
info: DeviceInfo::new(id, name, Protocol::ModbusTcp),
}
}
}
#[async_trait]
impl Device for MockDevice {
fn info(&self) -> &DeviceInfo {
&self.info
}
async fn initialize(&mut self) -> Result<()> {
Ok(())
}
async fn start(&mut self) -> Result<()> {
Ok(())
}
async fn stop(&mut self) -> Result<()> {
Ok(())
}
async fn tick(&mut self) -> Result<()> {
Ok(())
}
fn point_definitions(&self) -> Vec<&DataPointDef> {
vec![]
}
fn point_definition(&self, _point_id: &str) -> Option<&DataPointDef> {
None
}
async fn read(&self, point_id: &str) -> Result<DataPoint> {
Ok(DataPoint::new(
DataPointId::new(&self.info.id, point_id),
Value::F64(0.0),
))
}
async fn write(&mut self, _point_id: &str, _value: Value) -> Result<()> {
Ok(())
}
fn statistics(&self) -> DeviceStatistics {
DeviceStatistics::default()
}
}
struct MockFactory;
impl DeviceFactory for MockFactory {
fn protocol(&self) -> Protocol {
Protocol::ModbusTcp
}
fn create(&self, config: DeviceConfig) -> Result<BoxedDevice> {
Ok(Box::new(MockDevice::new(&config.id, &config.name)))
}
}
struct MockPlugin {
name: String,
}
impl MockPlugin {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
impl Plugin for MockPlugin {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Mock plugin for testing"
}
fn register_factories(&self, registry: &FactoryRegistry) -> Result<()> {
registry.register(MockFactory)
}
}
#[test]
fn test_factory_registry() {
let registry = FactoryRegistry::new();
assert!(!registry.has(Protocol::ModbusTcp));
registry.register(MockFactory).unwrap();
assert!(registry.has(Protocol::ModbusTcp));
assert!(registry.protocols().contains(&Protocol::ModbusTcp));
}
#[test]
fn test_factory_metadata_uses_release_version() {
let metadata = MockFactory.metadata();
assert_eq!(metadata.version, RELEASE_VERSION);
}
#[test]
fn test_factory_create_device() {
let registry = FactoryRegistry::new();
registry.register(MockFactory).unwrap();
let config = DeviceConfig {
id: "test-001".to_string(),
name: "Test Device".to_string(),
description: String::new(),
protocol: Protocol::ModbusTcp,
address: None,
points: vec![],
metadata: Default::default(),
tags: Tags::new(),
};
let device = registry.create_device(config).unwrap();
assert_eq!(device.id(), "test-001");
}
#[test]
fn test_factory_validation() {
let factory = MockFactory;
let config = DeviceConfig {
id: String::new(),
name: "Test".to_string(),
description: String::new(),
protocol: Protocol::ModbusTcp,
address: None,
points: vec![],
metadata: Default::default(),
tags: Tags::new(),
};
assert!(factory.validate(&config).is_err());
let config = DeviceConfig {
id: "test".to_string(),
name: "Test".to_string(),
description: String::new(),
protocol: Protocol::OpcUa,
address: None,
points: vec![],
metadata: Default::default(),
tags: Tags::new(),
};
assert!(factory.validate(&config).is_err());
}
#[test]
fn test_plugin_manager() {
let registry = Arc::new(FactoryRegistry::new());
let manager = PluginManager::new(registry.clone());
assert_eq!(manager.plugin_count(), 0);
manager.load(MockPlugin::new("test-plugin")).unwrap();
assert_eq!(manager.plugin_count(), 1);
assert!(registry.has(Protocol::ModbusTcp));
let info = manager.plugin_info();
assert_eq!(info[0].name, "test-plugin");
assert_eq!(info[0].version, RELEASE_VERSION);
}
#[test]
fn test_duplicate_factory_registration() {
let registry = FactoryRegistry::new();
registry.register(MockFactory).unwrap();
let result = registry.register(MockFactory);
assert!(result.is_err());
}
}