Packages

BACstack is a low-level Elixir implementation for the ASHRAE standard 135, BACnet - Building Automation and Controller network. In its current published state, v0.0.x, it can (only) act as a BACnet client for other BACnet devices.

Current section

Files

Jump to
bacstack lib bacnet protocol days_of_week.ex
Raw

lib/bacnet/protocol/days_of_week.ex

defmodule BACnet.Protocol.DaysOfWeek do
  @moduledoc """
  DaysOfWeek is a 7-bit bit string (application tag 8) where each bit represents
  one day of the week. It is one of the most frequently used building blocks for
  recurring schedules and exception handling in BACnet.

  Bit layout (MSB first, per 20.2.10 encoding rules):

  | Bit | Day       | Field in struct    |
  |-----|-----------|--------------------|
  | 0   | Monday    | `monday`           |
  | 1   | Tuesday   | `tuesday`          |
  | 2   | Wednesday | `wednesday`        |
  | 3   | Thursday  | `thursday`         |
  | 4   | Friday    | `friday`           |
  | 5   | Saturday  | `saturday`         |
  | 6   | Sunday    | `sunday`           |

  A bit set to `true` means the day is included in the set.

  ### BACnet Specification References
  - **ASN.1** (Clause 21): `BACnetDaysOfWeek ::= BIT STRING { monday (0), tuesday (1), wednesday (2), thursday (3), friday (4), saturday (5), sunday (6) }`
  - **Primary uses**:
    - `weekly_schedule` in Schedule objects (one `DailySchedule` per day of week)
    - `exception_schedule` (combined with `WeekNDay` or `DateRange`)
    - `valid_days` in `BACnet.Protocol.Destination` (who receives notifications on which days)
    - Many calendar and special-event constructs

  ### Examples (Doc Test)

  ```elixir
  iex> weekdays = %DaysOfWeek{
  ...>   monday: true,
  ...>   tuesday: true,
  ...>   wednesday: true,
  ...>   thursday: true,
  ...>   friday: true,
  ...>   saturday: false,
  ...>   sunday: false
  ...> }
  iex> weekdays.friday
  true
  ```

  ### See Also
  - `BACnet.Protocol.CalendarEntry`
  - `BACnet.Protocol.DailySchedule`
  - `BACnet.Protocol.DateRange`
  - `BACnet.Protocol.Destination`
  - `BACnet.Protocol.WeekNDay`
  """

  # TODO: Throw argument error in encode if not valid

  alias BACnet.Protocol.ApplicationTags

  @typedoc """
  Represents a BACnet days of week.
  """
  @type t :: %__MODULE__{
          monday: boolean(),
          tuesday: boolean(),
          wednesday: boolean(),
          thursday: boolean(),
          friday: boolean(),
          saturday: boolean(),
          sunday: boolean()
        }

  @fields [
    :monday,
    :tuesday,
    :wednesday,
    :thursday,
    :friday,
    :saturday,
    :sunday
  ]
  @enforce_keys @fields
  defstruct @fields

  @doc """
  Encodes a BACnet days of week into application tags encoding.
  """
  @spec encode(t(), Keyword.t()) :: {:ok, ApplicationTags.encoding_list()} | {:error, term()}
  def encode(%__MODULE__{} = flags, _opts \\ []) do
    {:ok, [to_bitstring(flags)]}
  end

  @doc """
  Creates from an application tag bitstring a days of week.
  """
  @spec from_bitstring(tuple()) :: t()
  def from_bitstring(bitstring) when is_tuple(bitstring) and tuple_size(bitstring) == 7 do
    %__MODULE__{
      monday: elem(bitstring, 0),
      tuesday: elem(bitstring, 1),
      wednesday: elem(bitstring, 2),
      thursday: elem(bitstring, 3),
      friday: elem(bitstring, 4),
      saturday: elem(bitstring, 5),
      sunday: elem(bitstring, 6)
    }
  end

  @doc """
  Parses a BACnet days of week from application tags encoding.
  """
  @spec parse(ApplicationTags.encoding_list()) ::
          {:ok, {t(), rest :: ApplicationTags.encoding_list()}}
          | {:error, term()}
  def parse(tags) when is_list(tags) do
    case tags do
      [{:bitstring, {_b1, _b2, _b3, _b4, _b5, _b6, _b7} = bs} | rest] ->
        {:ok, {from_bitstring(bs), rest}}

      _else ->
        {:error, :invalid_tags}
    end
  end

  @doc """
  Creates an application tag bitstring from a days of week.
  """
  @spec to_bitstring(t()) :: BACnet.Protocol.ApplicationTags.primitive_encoding()
  def to_bitstring(%__MODULE__{} = t) do
    {:bitstring, {t.monday, t.tuesday, t.wednesday, t.thursday, t.friday, t.saturday, t.sunday}}
  end

  @doc """
  Validates whether the given days of week is in form valid.

  It only validates the struct is valid as per type specification.
  """
  @spec valid?(t()) :: boolean()
  def valid?(
        %__MODULE__{
          monday: monday,
          tuesday: tuesday,
          wednesday: wednesday,
          thursday: thursday,
          friday: friday,
          saturday: saturday,
          sunday: sunday
        } = _t
      )
      when is_boolean(monday) and is_boolean(tuesday) and is_boolean(wednesday) and
             is_boolean(thursday) and is_boolean(friday) and is_boolean(saturday) and
             is_boolean(sunday),
      do: true

  def valid?(%__MODULE__{} = _t), do: false
end