Skip to main content

mabi_knx/
lib.rs

1//! # mabi-knx
2//!
3//! KNXnet/IP simulator for the industrial protocol simulator.
4//!
5//! This crate provides a complete KNXnet/IP implementation including:
6//!
7//! - **Address System**: Individual and Group address handling
8//! - **DPT System**: Extensible Datapoint Type encoding/decoding
9//! - **cEMI Frames**: Common EMI frame handling
10//! - **KNXnet/IP Protocol**: Full protocol stack with tunneling support
11//! - **Device Abstraction**: Integration with mabi-core
12//!
13//! ## Quick Start
14//!
15//! ```rust,ignore
16//! use mabi_knx::{
17//!     KnxServer, KnxServerConfig, KnxDevice, KnxDeviceBuilder,
18//!     GroupAddress, IndividualAddress, DptId,
19//! };
20//!
21//! // Create a KNX device
22//! let device = KnxDeviceBuilder::new("knx-1", "Living Room Controller")
23//!     .individual_address(IndividualAddress::new(1, 1, 1))
24//!     .group_object("1/0/1".parse()?, "Light Switch", "1.001".parse()?)
25//!     .build()?;
26//!
27//! // Create a server
28//! let config = KnxServerConfig::default();
29//! let server = KnxServer::new(config).await?;
30//! server.start().await?;
31//! ```
32//!
33//! ## Module Overview
34//!
35//! - [`error`]: Error types and result definitions
36//! - [`config`]: Configuration structures for server and devices
37//! - [`address`]: Individual and Group address types
38//! - [`dpt`]: Datapoint Type system with extensible codec support
39//! - [`cemi`]: Common EMI frame handling
40//! - [`frame`]: KNXnet/IP frame and service types
41//! - [`group`]: Group object table and event system
42//! - [`tunnel`]: Tunneling connection management
43//! - [`server`]: KNXnet/IP server implementation
44//! - [`device`]: KNX device implementing core Device trait
45//! - [`factory`]: Device factory for creating KNX devices
46
47// Core modules
48pub mod error;
49pub mod config;
50pub mod address;
51
52// Protocol modules
53pub mod dpt;
54pub mod cemi;
55pub mod frame;
56
57// Device and server modules
58pub mod group;
59pub mod group_cache;
60pub mod tunnel;
61pub mod filter;
62pub mod heartbeat;
63pub mod error_tracker;
64pub mod metrics;
65pub mod diagnostics;
66pub mod server;
67pub mod device;
68pub mod factory;
69
70// Re-exports for convenience
71pub use error::{KnxError, KnxResult};
72pub use config::{
73    KnxServerConfig, KnxDeviceConfig, GroupObjectConfig,
74    GroupObjectFlagsConfig, TunnelConfig,
75};
76pub use address::{GroupAddress, IndividualAddress, AddressType, GroupAddressRange};
77pub use dpt::{
78    DptId, DptValue, DptCodec, DptRegistry, BoxedDptCodec,
79    encode_dpt9, decode_dpt9,
80};
81pub use cemi::{CemiFrame, MessageCode, Priority, Apci, AdditionalInfo, AdditionalInfoType};
82pub use frame::{KnxFrame, FrameBuilder, KnxNetIpHeader, ServiceType, Hpai, DibDeviceInfo, SupportedServiceFamilies, ServiceFamily};
83pub use group::{GroupObject, GroupObjectTable, GroupObjectFlags, GroupEvent};
84pub use tunnel::{
85    TunnelConnection,
86    ConnectRequest, ConnectResponse, ConnectionType,
87    TunnellingRequest, TunnellingAck,
88    ConnectionStateRequest, ConnectionStateResponse,
89    DisconnectRequest, DisconnectResponse,
90    // Phase 1: Sequence tracking, ACK management, FSM
91    SequenceTracker, ReceivedValidation, AckValidation, SequenceStatsSnapshot,
92    AckWaiter, AckResult, AckMessage, AckWaiterStatsSnapshot,
93    TunnelFsm, TunnelState, TunnelErrorReason, FsmStatsSnapshot,
94};
95pub use config::TunnelBehaviorConfig;
96pub use filter::{
97    FilterChain, FilterChainConfig, FilterChainStats, FilterChainStatsSnapshot,
98    FilterDirection, FilterResult, FrameEnvelope,
99    PaceFilter, PaceFilterConfig, PaceState, PaceFilterStats, PaceFilterStatsSnapshot,
100    QueueFilter, QueueFilterConfig, QueuePriority, QueueFilterStats, QueueFilterStatsSnapshot,
101    RetryFilter, RetryFilterConfig, CircuitBreakerState, RetryFilterStats, RetryFilterStatsSnapshot,
102};
103pub use heartbeat::{
104    HeartbeatAction, HeartbeatSchedule, HeartbeatScheduler, HeartbeatSchedulerConfig,
105    HeartbeatStatsSnapshot,
106};
107pub use group_cache::{
108    GroupValueCache, GroupValueCacheConfig, CacheEntry, CacheEntryInfo,
109    CacheStatsSnapshot, UpdateSource,
110};
111pub use error_tracker::{
112    SendErrorTracker, SendErrorTrackerConfig, ErrorCategory, TrackingResult,
113    ChannelErrorSummary, TrackerStatsSnapshot,
114};
115pub use metrics::{
116    KnxMetricsSnapshot, KnxMetricsCollector, ConnectionMetricsSnapshot,
117};
118pub use diagnostics::{
119    KnxDiagnostics, DiagnosticResult, DiagnosticSeverity, DiagnosticRule,
120    DiagnosticConfig,
121};
122pub use server::{KnxServer, ServerState, ServerEvent, ConnectionManager};
123pub use device::{KnxDevice, KnxDeviceBuilder};
124pub use factory::{KnxDeviceFactory, register_knx_factory};
125
126/// Protocol version for KNXnet/IP
127pub const KNXNETIP_VERSION: u8 = 0x10;
128
129/// Default KNXnet/IP port
130pub const DEFAULT_PORT: u16 = 3671;
131
132/// Default multicast address for KNX discovery
133pub const DEFAULT_MULTICAST_ADDR: &str = "224.0.23.12";
134
135/// Prelude module for convenient imports
136pub mod prelude {
137    pub use crate::{
138        // Error handling
139        KnxError, KnxResult,
140        // Addresses
141        GroupAddress, IndividualAddress,
142        // DPT
143        DptId, DptValue, DptCodec, DptRegistry,
144        // Device and server
145        KnxDevice, KnxDeviceBuilder,
146        KnxServer, KnxServerConfig,
147        // Group objects
148        GroupObject, GroupObjectTable, GroupEvent,
149        // Configuration
150        KnxDeviceConfig, GroupObjectConfig,
151    };
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::cemi::MessageCode;
158
159    #[test]
160    fn test_group_address_parsing() {
161        let addr: GroupAddress = "1/2/3".parse().unwrap();
162        assert_eq!(addr.main(), 1);
163        assert_eq!(addr.middle(), 2);
164        assert_eq!(addr.sub(), 3);
165        assert_eq!(addr.to_string(), "1/2/3");
166    }
167
168    #[test]
169    fn test_individual_address_parsing() {
170        let addr: IndividualAddress = "1.2.3".parse().unwrap();
171        assert_eq!(addr.area(), 1);
172        assert_eq!(addr.line(), 2);
173        assert_eq!(addr.device(), 3);
174        assert_eq!(addr.to_string(), "1.2.3");
175    }
176
177    #[test]
178    fn test_dpt_id_parsing() {
179        let dpt: DptId = "1.001".parse().unwrap();
180        assert_eq!(dpt.main, 1);
181        assert_eq!(dpt.sub, 1);
182        // Display format is "DPT X.XXX"
183        assert!(dpt.to_string().contains("1.001"));
184    }
185
186    #[test]
187    fn test_dpt_registry_lookup() {
188        let registry = DptRegistry::new();
189
190        // Standard DPTs should be registered
191        assert!(registry.get(&DptId::new(1, 1)).is_some());
192        assert!(registry.get(&DptId::new(5, 1)).is_some());
193        assert!(registry.get(&DptId::new(9, 1)).is_some());
194    }
195
196    #[test]
197    fn test_service_type_round_trip() {
198        let service = ServiceType::TunnellingRequest;
199        let code: u16 = service.into();
200        let parsed = ServiceType::try_from(code).unwrap();
201        assert_eq!(parsed, service);
202    }
203
204    #[test]
205    fn test_cemi_frame_creation() {
206        let frame = CemiFrame::group_value_write(
207            IndividualAddress::new(1, 1, 1),
208            GroupAddress::three_level(1, 0, 1),
209            vec![0x00], // Value 0
210        );
211
212        assert_eq!(frame.message_code, MessageCode::LDataInd);
213    }
214
215    #[test]
216    fn test_group_address_range() {
217        let range = GroupAddressRange::new(
218            GroupAddress::three_level(1, 0, 0),
219            GroupAddress::three_level(1, 0, 255),
220        );
221
222        assert!(range.contains(&GroupAddress::three_level(1, 0, 100)));
223        assert!(!range.contains(&GroupAddress::three_level(1, 1, 0)));
224    }
225
226    #[test]
227    fn test_dpt_value_encoding() {
228        // Test boolean encoding
229        let registry = DptRegistry::new();
230        let codec = registry.get(&DptId::new(1, 1)).unwrap();
231
232        let encoded = codec.encode(&DptValue::Bool(true)).unwrap();
233        assert_eq!(encoded, vec![0x01]);
234
235        let decoded = codec.decode(&[0x01]).unwrap();
236        assert_eq!(decoded, DptValue::Bool(true));
237    }
238
239    #[test]
240    fn test_knx_device_builder() {
241        let device = KnxDeviceBuilder::new("test-device", "Test Device")
242            .individual_address(IndividualAddress::new(1, 1, 1))
243            .description("A test device")
244            .build()
245            .unwrap();
246
247        assert_eq!(device.id(), "test-device");
248        assert_eq!(device.individual_address().to_string(), "1.1.1");
249    }
250
251    #[test]
252    fn test_factory_protocol() {
253        use mabi_core::{DeviceFactory, Protocol};
254
255        let factory = KnxDeviceFactory::new();
256        assert_eq!(factory.protocol(), Protocol::KnxIp);
257    }
258}