Current section
Files
Jump to
Current section
Files
lib/bacnet/protocol/services/acknowledge_alarm.ex
defmodule BACnet.Protocol.Services.AcknowledgeAlarm do
@moduledoc """
This module represents the BACnet Acknowledge Alarm service.
The Acknowledge Alarm service is used to acknowledge a human operator has seen the alarm notification.
Service Description (ASHRAE 135):
> In some systems a device may need to know that an operator has seen the alarm notification. The AcknowledgeAlarm service
> is used by a notification-client to acknowledge that a human operator has seen and responded to an event notification with
> 'AckRequired' = TRUE. Ensuring that the acknowledgment actually comes from a person with appropriate authority is a local
> matter. This service may be used in conjunction with either the ConfirmedEventNotification service or the
> UnconfirmedEventNotification service.
"""
alias BACnet.Protocol
alias BACnet.Protocol.ApplicationTags
alias BACnet.Protocol.Constants
import Protocol.Utility, only: [pattern_extract_tags: 4]
require Constants
@behaviour Protocol.Services.Behaviour
# TODO: Docs
# TODO: Add Service Procedure to docs
@type t :: %__MODULE__{
process_identifier: ApplicationTags.unsigned32(),
event_object: Protocol.ObjectIdentifier.t(),
event_state_acknowledged: Constants.event_state(),
event_timestamp: Protocol.BACnetTimestamp.t(),
acknowledge_source: String.t(),
time_of_ack: Protocol.BACnetTimestamp.t()
}
@fields [
:process_identifier,
:event_object,
:event_state_acknowledged,
:event_timestamp,
:acknowledge_source,
:time_of_ack
]
@enforce_keys @fields
defstruct @fields
@service_name Constants.macro_assert_name(
:confirmed_service_choice,
:acknowledge_alarm
)
@doc """
Get the service name atom.
"""
@spec get_name() :: atom()
def get_name(), do: @service_name
@doc """
Whether the service is of type confirmed or unconfirmed.
"""
@spec is_confirmed() :: true
def is_confirmed(), do: true
@doc """
Converts the given Confirmed Service Request into an Acknowledge Alarm Service.
"""
@spec from_apdu(Protocol.APDU.ConfirmedServiceRequest.t()) ::
{:ok, t()} | {:error, term()}
def from_apdu(
%Protocol.APDU.ConfirmedServiceRequest{
service: @service_name
} = request
) do
with {:ok, process_identifier, rest} <-
pattern_extract_tags(
request.parameters,
{:tagged, {0, _t, _l}},
:unsigned_integer,
false
),
:ok <-
if(ApplicationTags.valid_int?(process_identifier, 32),
do: :ok,
else: {:error, :invalid_process_identifier_value}
),
{:ok, object_identifier, rest} <-
pattern_extract_tags(rest, {:tagged, {1, _t, _l}}, :object_identifier, false),
{:ok, event_state, rest} <-
pattern_extract_tags(rest, {:tagged, {2, _t, _l}}, :enumerated, false),
{:ok, event_state_c} <-
Constants.by_value_with_reason(
:event_state,
event_state,
{:unknown_event_state, event_state}
),
{:ok, {:constructed, {_tag, timestamp_raw, _len}}, rest} <-
pattern_extract_tags(rest, {:constructed, {3, _t, _l}}, nil, false),
{:ok, {timestamp, _rest}} <- Protocol.BACnetTimestamp.parse(List.wrap(timestamp_raw)),
{:ok, source, rest} <-
(case pattern_extract_tags(rest, {:tagged, {4, _t, _l}}, :character_string, false) do
{:ok, _source, _rest} = source ->
source
err ->
# ASHRAE 135 - 13.5.2
# A device shall not fail to process, or issue a Result(-), upon receiving an AcknowledgeAlarm
# service request containing an 'Acknowledgment Source' parameter in an unsupported character set.
# In this case, it is a local matter whether the 'Acknowledgment Source' parameter is used as
# provided or whether a character string, in a supported character set, of length
# 0 is used in its place.
case rest do
[{:tagged, {4, _t, _l}} | tl] -> {:ok, "", tl}
_term -> err
end
end),
{:ok, {:constructed, {_tag, acktime_raw, _len}}, _rest} <-
pattern_extract_tags(rest, {:constructed, {5, _t, _l}}, nil, false),
{:ok, {acktime, _rest}} <- Protocol.BACnetTimestamp.parse(List.wrap(acktime_raw)) do
ack = %__MODULE__{
process_identifier: process_identifier,
event_object: object_identifier,
event_state_acknowledged: event_state_c,
event_timestamp: timestamp,
acknowledge_source: source,
time_of_ack: acktime
}
{:ok, ack}
else
{:error, :missing_pattern} -> {:error, :invalid_request_parameters}
{:error, _err} = err -> err
end
end
def from_apdu(%Protocol.APDU.ConfirmedServiceRequest{} = _request) do
{:error, :invalid_request}
end
@doc """
Get the Confirmed Service request for the given Acknowledge Alarm Service.
See the `BACnet.Protocol.Services.Protocol` function documentation for more information.
"""
@spec to_apdu(t(), Keyword.t()) ::
{:ok, Protocol.APDU.ConfirmedServiceRequest.t()}
| {:error, term()}
def to_apdu(%__MODULE__{} = service, request_data) do
with :ok <-
if(ApplicationTags.valid_int?(service.process_identifier, 32),
do: :ok,
else: {:error, :invalid_process_identifier_value}
),
{:ok, process_identifier, _header} <-
ApplicationTags.encode_value({:unsigned_integer, service.process_identifier}),
{:ok, object_identifier, _header} <-
ApplicationTags.encode_value({:object_identifier, service.event_object}),
{:ok, state_ackd_c} <-
Constants.by_name_with_reason(
:event_state,
service.event_state_acknowledged,
{:unknown_event_state, service.event_state_acknowledged}
),
{:ok, state_ackd, _header} <-
ApplicationTags.encode_value({:unsigned_integer, state_ackd_c}),
{:ok, [timestamp]} <- Protocol.BACnetTimestamp.encode(service.event_timestamp),
{:ok, charstr, _header} <-
ApplicationTags.encode_value({:character_string, service.acknowledge_source}),
{:ok, [acktime]} <- Protocol.BACnetTimestamp.encode(service.time_of_ack) do
params = [
{:tagged, {0, process_identifier, byte_size(process_identifier)}},
{:tagged, {1, object_identifier, byte_size(object_identifier)}},
{:tagged, {2, state_ackd, byte_size(state_ackd)}},
{:constructed, {3, timestamp, 0}},
{:tagged, {4, charstr, byte_size(charstr)}},
{:constructed, {5, acktime, 0}}
]
req = %Protocol.APDU.ConfirmedServiceRequest{
segmented_response_accepted: request_data[:segmented_response_accepted] || true,
max_segments: request_data[:max_segments] || :more_than_64,
max_apdu:
request_data[:max_apdu] ||
Constants.macro_by_name(:max_apdu_length_accepted_value, :octets_1476),
invoke_id: request_data[:invoke_id] || 0,
sequence_number: nil,
proposed_window_size: nil,
service: @service_name,
parameters: params
}
{:ok, req}
end
end
defimpl Protocol.Services.Protocol do
alias BACnet.Protocol
alias BACnet.Protocol.Constants
require Constants
@spec get_name(@for.t()) :: atom()
def get_name(%@for{} = _service), do: @for.get_name()
@spec is_confirmed(@for.t()) :: boolean()
def is_confirmed(%@for{} = _service), do: @for.is_confirmed()
@spec to_apdu(@for.t(), Keyword.t()) ::
{:ok, Protocol.APDU.UnconfirmedServiceRequest.t()}
| {:error, term()}
def to_apdu(%@for{} = service, request_data), do: @for.to_apdu(service, request_data)
end
end