bacnet-client 0.12.0

BACnet async client: TSM, segmentation, discovery, and service APIs
Documentation
use super::*;

use bacnet_encoding::apdu::{decode_apdu, SimpleAck};
use bacnet_services::alarm_event::AcknowledgeAlarmRequest;
use bacnet_transport::loopback::LoopbackTransport;
use bacnet_types::enums::{EventState, ObjectType};
use bacnet_types::primitives::{BACnetTimeStamp, Date, ObjectIdentifier, Time};

fn time(hour: u8) -> BACnetTimeStamp {
    BACnetTimeStamp::Time(Time {
        hour,
        minute: 2,
        second: 3,
        hundredths: 4,
    })
}

fn date_time(day: u8) -> BACnetTimeStamp {
    BACnetTimeStamp::DateTime {
        date: Date {
            year: 126,
            month: 9,
            day,
            day_of_week: 3,
        },
        time: Time {
            hour: 5,
            minute: 6,
            second: 7,
            hundredths: 8,
        },
    }
}

#[tokio::test]
async fn canonical_acknowledge_alarm_preserves_every_caller_supplied_field() {
    let client_mac = vec![0x01];
    let remote_mac = vec![0x02];
    let (client_transport, remote_transport) =
        LoopbackTransport::pair(client_mac, remote_mac.clone());
    let mut remote_network = NetworkLayer::new(remote_transport);
    let mut remote_rx = remote_network.start().await.unwrap();
    let mut client = BACnetClient::generic_builder()
        .transport(client_transport)
        .build()
        .await
        .unwrap();

    let requests = vec![
        AcknowledgeAlarmRequest {
            acknowledging_process_identifier: 11,
            event_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
            event_state_acknowledged: EventState::HIGH_LIMIT,
            timestamp: BACnetTimeStamp::SequenceNumber(101),
            acknowledgment_source: "sequence-to-time".into(),
            time_of_acknowledgment: time(9),
        },
        AcknowledgeAlarmRequest {
            acknowledging_process_identifier: 22,
            event_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 2).unwrap(),
            event_state_acknowledged: EventState::FAULT,
            timestamp: time(10),
            acknowledgment_source: "time-to-date-time".into(),
            time_of_acknowledgment: date_time(2),
        },
        AcknowledgeAlarmRequest {
            acknowledging_process_identifier: 33,
            event_object_identifier: ObjectIdentifier::new(ObjectType::EVENT_ENROLLMENT, 3)
                .unwrap(),
            event_state_acknowledged: EventState::NORMAL,
            timestamp: date_time(3),
            acknowledgment_source: "date-time-to-sequence".into(),
            time_of_acknowledgment: BACnetTimeStamp::SequenceNumber(303),
        },
    ];
    let expected = requests.clone();

    let responder = tokio::spawn(async move {
        for expected_request in expected {
            let received = timeout(Duration::from_secs(1), remote_rx.recv())
                .await
                .expect("remote timed out")
                .expect("remote channel closed");
            let Apdu::ConfirmedRequest(request) = decode_apdu(received.apdu).unwrap() else {
                panic!("expected ConfirmedRequest");
            };
            assert_eq!(
                request.service_choice,
                ConfirmedServiceChoice::ACKNOWLEDGE_ALARM
            );
            assert_eq!(
                AcknowledgeAlarmRequest::decode(&request.service_request).unwrap(),
                expected_request
            );

            let mut ack = BytesMut::new();
            encode_apdu(
                &mut ack,
                &Apdu::SimpleAck(SimpleAck {
                    invoke_id: request.invoke_id,
                    service_choice: request.service_choice,
                }),
            )
            .unwrap();
            remote_network
                .send_apdu(&ack, &received.source_mac, false, NetworkPriority::NORMAL)
                .await
                .unwrap();
        }
        remote_network.stop().await.unwrap();
    });

    for request in &requests {
        client
            .acknowledge_alarm_request(&remote_mac, request)
            .await
            .unwrap();
    }

    responder.await.unwrap();
    client.stop().await.unwrap();
}