defmodule BACstack.MixProject do use Mix.Project @source_url "https://github.com/bacnet-ex/bacstack" def project() do [ app: :bacstack, version: "0.0.1", elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), elixirc_options: [tracers: add_compilation_tracer()], # This is the cause for unknown protocol __impl__/1 for built-in types consolidate_protocols: Mix.env() == :prod, start_permanent: Mix.env() == :prod, deps: deps(), description: description(), package: package(), dialyzer: [ ignore_warnings: "dialyzer.ignore-warnings.exs", plt_add_apps: [:mix] ], source_url: @source_url, docs: docs(), test_coverage: [ ignore_modules: [ ~r"Inspect\..+", BACnet.Macro, ~r"BACnet\.Protocol\.Constants\..+", BACnet.Protocol.ObjectsUtility.Internal.ReadPropertyAckTransformOptions, BACnet.Protocol.Services.Protocol, BACnet.Stack.EncoderProtocol, ~r"BACnet\.Test\..+", # TODO: Remove me once we can test services error modules (when we have payloads) ~r"BACnet\.Protocol\.Services\.Error\..+" ], summary: true, tool: if(System.get_env("CI"), do: ExCoveralls, else: Mix.Tasks.Test.Coverage) ] ] end def application() do [ extra_applications: [:inets, :logger] ] end defp deps() do # html_entities and req are for downloading vendor IDs from the BACnet website [ {:cidr, "~> 1.1"}, {:codepagex, "~> 0.1"}, {:const_enum, "~> 0.1.0"}, {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, {:dialyxir, "1.4.3", only: [:dev, :test], runtime: false}, {:doctor, "~> 0.21", only: [:dev, :test], runtime: false}, {:ex_doc, "~> 0.29", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.18", only: [:test], runtime: false}, {:html_entities, "~> 0.5.2", only: [:dev, :test], optional: true, runtime: false}, {:junit_formatter, "~> 3.3", only: [:test], runtime: false}, {:req, "~> 0.5.15", only: [:dev, :test], optional: true, runtime: false}, {:telemetry, "~> 1.3", optional: true} ] end # Specifies which paths to compile per environment defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] defp description() do "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." end defp package() do [ files: ~w(lib priv .formatter.exs mix.exs README* LICENSE*), licenses: ["Apache-2.0"], links: %{"GitHub" => @source_url} ] end defp docs() do [ main: "BACnet", source_ref: "master", filter_modules: ~r"^Elixir\.BACnet(?!\.Test).*$", groups_for_docs: [Guards: & &1[:guard]], groups_for_modules: [ "Protocol: General": [ ~r"Protocol\.APDU", ~r"Protocol\.NPCI"i, ~r"Protocol\.NPDU"i, BACnet.Protocol, BACnet.Protocol.ApplicationTags, BACnet.Protocol.ApplicationTags.Encoding, BACnet.Protocol.Constants, BACnet.Protocol.IncompleteAPDU, BACnet.Protocol.NetworkLayerProtocolMessage ], "Protocol: Data Types": [ ~r"Protocol\.BACnet", BACnet.Protocol.AccessSpecification, BACnet.Protocol.AccessSpecification.Property, BACnet.Protocol.AccumulatorRecord, BACnet.Protocol.ActionCommand, BACnet.Protocol.ActionList, BACnet.Protocol.AddressBinding, BACnet.Protocol.AlarmSummary, BACnet.Protocol.BACnetArray, BACnet.Protocol.CalendarEntry, BACnet.Protocol.CovSubscription, BACnet.Protocol.DailySchedule, BACnet.Protocol.DateRange, BACnet.Protocol.DaysOfWeek, BACnet.Protocol.Destination, BACnet.Protocol.DeviceObjectRef, BACnet.Protocol.DeviceObjectPropertyRef, BACnet.Protocol.EnrollmentSummary, BACnet.Protocol.EventInformation, BACnet.Protocol.EventLogRecord, BACnet.Protocol.EventMessageTexts, BACnet.Protocol.EventTimestamps, BACnet.Protocol.EventTransitionBits, BACnet.Protocol.GroupChannelValue, BACnet.Protocol.LimitEnable, BACnet.Protocol.LogMultipleRecord, BACnet.Protocol.LogRecord, BACnet.Protocol.LogStatus, BACnet.Protocol.NotificationClassPriority, BACnet.Protocol.ObjectIdentifier, BACnet.Protocol.ObjectPropertyRef, BACnet.Protocol.Prescale, BACnet.Protocol.PriorityArray, BACnet.Protocol.PropertyRef, BACnet.Protocol.PropertyState, BACnet.Protocol.PropertyValue, BACnet.Protocol.ReadAccessResult, BACnet.Protocol.ReadAccessResult.ReadResult, BACnet.Protocol.Recipient, BACnet.Protocol.RecipientAddress, BACnet.Protocol.ResultFlags, BACnet.Protocol.SetpointReference, BACnet.Protocol.SpecialEvent, BACnet.Protocol.StatusFlags, BACnet.Protocol.TimeValue, BACnet.Protocol.WeekNDay ], "Protocol: Objects": [ BACnet.Protocol.ObjectsUtility, ~r"Protocol\.ObjectTypes\..+", BACnet.Protocol.Device.ObjectTypesSupported, BACnet.Protocol.Device.ServicesSupported ], "Protocol: Services": [~r"Protocol\.Services\.(?!Behaviour|Common|Protocol)"], "Protocol: Alarm & Event Subscription": [ ~r"Protocol\.EventAlgorithms", ~r"Protocol\.EventParameters", ~r"Protocol\.FaultAlgorithms", ~r"Protocol\.FaultParameters", ~r"Protocol\.NotificationParameters" ], "Protocol: BACnet/IP": [ ~r"Protocol\.Bvlc", BACnet.Protocol.BroadcastDistributionTableEntry, BACnet.Protocol.ForeignDeviceTableEntry ], Stack: [~r"Stack\.[^.]+$", ~r"Stack\.Transport\.[^.]+$"], "BACstack Internals": [ BACnet.Internal, BACnet.Protocol.ObjectsMacro, BACnet.Protocol.Services.Behaviour, BACnet.Protocol.Services.Common, BACnet.Protocol.Services.Protocol, BACnet.Protocol.Utility, BACnet.Stack.BBMD.ClientRef, BACnet.Stack.BBMD.Registration, BACnet.Stack.BBMD.State, BACnet.Stack.Client.ApduTimer, BACnet.Stack.Client.ReplyTimer, BACnet.Stack.Client.State, BACnet.Stack.ForeignDevice.Registration, BACnet.Stack.ForeignDevice.State, BACnet.Stack.Segmentator.Sequence, BACnet.Stack.Segmentator.State, BACnet.Stack.SegmentsStore.Sequence, BACnet.Stack.SegmentsStore.State, BACnet.Stack.TrendLogger.Log, BACnet.Stack.TrendLogger.State ] ], nest_modules_by_prefix: [ BACnet.Protocol, BACnet.Protocol.EventAlgorithms, BACnet.Protocol.EventParameters, BACnet.Protocol.FaultAlgorithms, BACnet.Protocol.FaultParameters, BACnet.Protocol.NotificationParameters, BACnet.Protocol.ObjectTypes, BACnet.Protocol.Services, BACnet.Protocol.Services.Ack, BACnet.Protocol.Services.Error, BACnet.Stack, BACnet.Stack.Transport ], before_closing_head_tag: &docs_before_closing_head_tag/1, before_closing_body_tag: &docs_before_closing_body_tag/1 ] end defp docs_before_closing_head_tag(:html) do """ """ end defp docs_before_closing_head_tag(:epub), do: "" defp docs_before_closing_body_tag(:html) do """ """ end defp docs_before_closing_body_tag(:epub), do: "" #### Compilation tracer START #### # We want to be able to lookup module bytecodes for freshly compiled modules, # for already compiled modules we can just lookup the BEAM file as fallback, # so we can retrieve the types from the BEAM bytecode for our object types macro module @doc """ Adds the current module as compilation tracer to the list of compilation tracers in the Elixir compiler. The return value is meant to be added to `project/0`'s `elixirc_options`. If you do not do BACnet low-level development and create new object implementations, for example. As a simple library user this is not needed. For example: ```ex Code.ensure_loaded!(BACstack.MixProject) // Inside your mix.exs elixirc_options: [tracers: BACstack.MixProject.add_compilation_tracer()], // Or outside of your mix.exs: Code.put_compiler_option(:tracers, BACstack.MixProject.add_compilation_tracer()) ``` The compilation tracer is used to capture freshly compiled module bytecode, for use with lookup BEAM typespecs. Fresh compiled modules in the "first" compilation run are not yet flushed to a BEAM bytecode file, so compilation tracing needs to be used in that code. For all other modules that are already written to disk, the typespecs can be directly retrieved from the BEAM file. """ @spec add_compilation_tracer() :: list() def add_compilation_tracer() do [__MODULE__ | Code.get_compiler_option(:tracers)] end @doc false def trace({:on_module, bytecode, _ignore}, env) do # TODO: Review :persistent_term.put(env.module, bytecode) # IO.inspect(:persistent_term.info()) :ok end def trace(_event, _env) do :ok end #### Compilation tracer END #### #### Download BACnet Vendor IDs list from the BACnet website (HTML format and convert) #### @spec get_vendor_ids_csv_file() :: Path.t() def get_vendor_ids_csv_file() do Path.join([__DIR__, "priv", "vendor_ids.csv"]) end @spec get_vendor_ids() :: %{optional(non_neg_integer()) => String.t()} def get_vendor_ids() do if not File.exists?(get_vendor_ids_csv_file()) do Mix.Task.run("bacnet.dl.vendorids") end parse_vendor_ids_csv() end @spec parse_vendor_ids_csv() :: %{optional(non_neg_integer()) => String.t()} def parse_vendor_ids_csv() do get_vendor_ids_csv_file() |> File.read!() |> String.split("\n") |> Stream.flat_map(fn line -> case String.split(line, ";") do [id, name] -> [{String.to_integer(id), name}] _else -> [] end end) |> Map.new() end #### Download BACnet Vendor IDs list from the BACnet website END #### end