use crate::error::KnxError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum ServiceType {
SearchRequest = 0x0201,
SearchResponse = 0x0202,
DescriptionRequest = 0x0203,
DescriptionResponse = 0x0204,
ConnectRequest = 0x0205,
ConnectResponse = 0x0206,
ConnectionStateRequest = 0x0207,
ConnectionStateResponse = 0x0208,
DisconnectRequest = 0x0209,
DisconnectResponse = 0x020A,
SearchRequestExtended = 0x020B,
SearchResponseExtended = 0x020C,
DeviceConfigurationRequest = 0x0310,
DeviceConfigurationAck = 0x0311,
TunnellingRequest = 0x0420,
TunnellingAck = 0x0421,
TunnellingFeatureGet = 0x0422,
TunnellingFeatureResponse = 0x0423,
TunnellingFeatureSet = 0x0424,
TunnellingFeatureInfo = 0x0425,
RoutingIndication = 0x0530,
RoutingLostMessage = 0x0531,
RoutingBusy = 0x0532,
RoutingSystemBroadcast = 0x0533,
RemoteDiagnosticRequest = 0x0740,
RemoteDiagnosticResponse = 0x0741,
RemoteBasicConfigRequest = 0x0742,
RemoteResetRequest = 0x0743,
ObjectServerRequest = 0x0800,
}
impl ServiceType {
pub fn family(&self) -> ServiceFamily {
match (*self as u16) >> 8 {
0x02 => ServiceFamily::Core,
0x03 => ServiceFamily::DeviceManagement,
0x04 => ServiceFamily::Tunnelling,
0x05 => ServiceFamily::Routing,
0x06 => ServiceFamily::RemoteLogging,
0x07 => ServiceFamily::RemoteConfiguration,
0x08 => ServiceFamily::ObjectServer,
_ => ServiceFamily::Unknown,
}
}
pub fn is_request(&self) -> bool {
matches!(
self,
Self::SearchRequest
| Self::DescriptionRequest
| Self::ConnectRequest
| Self::ConnectionStateRequest
| Self::DisconnectRequest
| Self::DeviceConfigurationRequest
| Self::TunnellingRequest
| Self::TunnellingFeatureGet
| Self::TunnellingFeatureSet
| Self::RemoteDiagnosticRequest
| Self::RemoteBasicConfigRequest
| Self::RemoteResetRequest
| Self::ObjectServerRequest
)
}
pub fn is_response(&self) -> bool {
matches!(
self,
Self::SearchResponse
| Self::DescriptionResponse
| Self::ConnectResponse
| Self::ConnectionStateResponse
| Self::DisconnectResponse
| Self::TunnellingFeatureResponse
| Self::RemoteDiagnosticResponse
)
}
pub fn is_ack(&self) -> bool {
matches!(self, Self::DeviceConfigurationAck | Self::TunnellingAck)
}
pub fn is_tunnelling(&self) -> bool {
matches!(self.family(), ServiceFamily::Tunnelling)
}
pub fn is_routing(&self) -> bool {
matches!(self.family(), ServiceFamily::Routing)
}
pub fn is_core(&self) -> bool {
matches!(self.family(), ServiceFamily::Core)
}
pub fn expected_response(&self) -> Option<Self> {
match self {
Self::SearchRequest => Some(Self::SearchResponse),
Self::DescriptionRequest => Some(Self::DescriptionResponse),
Self::ConnectRequest => Some(Self::ConnectResponse),
Self::ConnectionStateRequest => Some(Self::ConnectionStateResponse),
Self::DisconnectRequest => Some(Self::DisconnectResponse),
Self::TunnellingRequest => Some(Self::TunnellingAck),
Self::DeviceConfigurationRequest => Some(Self::DeviceConfigurationAck),
Self::TunnellingFeatureGet => Some(Self::TunnellingFeatureResponse),
_ => None,
}
}
}
impl TryFrom<u16> for ServiceType {
type Error = KnxError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0x0201 => Ok(Self::SearchRequest),
0x0202 => Ok(Self::SearchResponse),
0x0203 => Ok(Self::DescriptionRequest),
0x0204 => Ok(Self::DescriptionResponse),
0x0205 => Ok(Self::ConnectRequest),
0x0206 => Ok(Self::ConnectResponse),
0x0207 => Ok(Self::ConnectionStateRequest),
0x0208 => Ok(Self::ConnectionStateResponse),
0x0209 => Ok(Self::DisconnectRequest),
0x020A => Ok(Self::DisconnectResponse),
0x020B => Ok(Self::SearchRequestExtended),
0x020C => Ok(Self::SearchResponseExtended),
0x0310 => Ok(Self::DeviceConfigurationRequest),
0x0311 => Ok(Self::DeviceConfigurationAck),
0x0420 => Ok(Self::TunnellingRequest),
0x0421 => Ok(Self::TunnellingAck),
0x0422 => Ok(Self::TunnellingFeatureGet),
0x0423 => Ok(Self::TunnellingFeatureResponse),
0x0424 => Ok(Self::TunnellingFeatureSet),
0x0425 => Ok(Self::TunnellingFeatureInfo),
0x0530 => Ok(Self::RoutingIndication),
0x0531 => Ok(Self::RoutingLostMessage),
0x0532 => Ok(Self::RoutingBusy),
0x0533 => Ok(Self::RoutingSystemBroadcast),
0x0740 => Ok(Self::RemoteDiagnosticRequest),
0x0741 => Ok(Self::RemoteDiagnosticResponse),
0x0742 => Ok(Self::RemoteBasicConfigRequest),
0x0743 => Ok(Self::RemoteResetRequest),
0x0800 => Ok(Self::ObjectServerRequest),
_ => Err(KnxError::UnknownServiceType(value)),
}
}
}
impl From<ServiceType> for u16 {
fn from(st: ServiceType) -> Self {
st as u16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServiceFamily {
Core,
DeviceManagement,
Tunnelling,
Routing,
RemoteLogging,
RemoteConfiguration,
ObjectServer,
Unknown,
}
impl ServiceFamily {
pub fn id(&self) -> u8 {
match self {
Self::Core => 0x02,
Self::DeviceManagement => 0x03,
Self::Tunnelling => 0x04,
Self::Routing => 0x05,
Self::RemoteLogging => 0x06,
Self::RemoteConfiguration => 0x07,
Self::ObjectServer => 0x08,
Self::Unknown => 0x00,
}
}
pub fn from_id(id: u8) -> Self {
match id {
0x02 => Self::Core,
0x03 => Self::DeviceManagement,
0x04 => Self::Tunnelling,
0x05 => Self::Routing,
0x06 => Self::RemoteLogging,
0x07 => Self::RemoteConfiguration,
0x08 => Self::ObjectServer,
_ => Self::Unknown,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Core => "KNXnet/IP Core",
Self::DeviceManagement => "KNXnet/IP Device Management",
Self::Tunnelling => "KNXnet/IP Tunnelling",
Self::Routing => "KNXnet/IP Routing",
Self::RemoteLogging => "KNXnet/IP Remote Logging",
Self::RemoteConfiguration => "KNXnet/IP Remote Configuration",
Self::ObjectServer => "KNXnet/IP Object Server",
Self::Unknown => "Unknown",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_service_type_conversion() {
assert_eq!(
ServiceType::try_from(0x0201).unwrap(),
ServiceType::SearchRequest
);
assert_eq!(u16::from(ServiceType::SearchRequest), 0x0201);
}
#[test]
fn test_service_type_family() {
assert_eq!(ServiceType::SearchRequest.family(), ServiceFamily::Core);
assert_eq!(
ServiceType::TunnellingRequest.family(),
ServiceFamily::Tunnelling
);
assert_eq!(
ServiceType::RoutingIndication.family(),
ServiceFamily::Routing
);
}
#[test]
fn test_service_type_is_request() {
assert!(ServiceType::SearchRequest.is_request());
assert!(ServiceType::ConnectRequest.is_request());
assert!(!ServiceType::SearchResponse.is_request());
}
#[test]
fn test_expected_response() {
assert_eq!(
ServiceType::ConnectRequest.expected_response(),
Some(ServiceType::ConnectResponse)
);
assert_eq!(
ServiceType::TunnellingRequest.expected_response(),
Some(ServiceType::TunnellingAck)
);
}
#[test]
fn test_unknown_service_type() {
assert!(ServiceType::try_from(0xFFFF).is_err());
}
}