Skip to content

Commit

Permalink
Add handle_start and refactor
Browse files Browse the repository at this point in the history
See the changes to `CHANGELOG.md` for a breakdown of this commit.
  • Loading branch information
danielspofford committed Jul 6, 2019
1 parent 45eaa67 commit 6afbb93
Show file tree
Hide file tree
Showing 27 changed files with 248 additions and 163 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
key: v1-{{ epoch }}
paths:
- _build
- run: MIX_ENV=prod mix dialyzer --halt-exit-status
- run: mix dialyzer --halt-exit-status
workflows:
version: 2
test_and_deploy:
Expand Down
2 changes: 1 addition & 1 deletion .credo.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used.
#
included: ["lib/", "src/", "test/", "web/", "apps/"],
included: ["lib/", "src/", "web/", "apps/"],
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
},
#
Expand Down
2 changes: 0 additions & 2 deletions .tool-versions

This file was deleted.

84 changes: 42 additions & 42 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
# v0.2.0
# v[VERSION]

## Breaking Changes

- `Apple`
- `deserialize/1` crashes on non-binary terms
- `ArrayedData`
- `deserialize/1` returns `{:error, bin}` instead of crashing on invalid input
- `Device`
- `deserialize/1`
- when attempting to deserialize a non-binary term, crash
- faults nested within a device discovered during deserialization will no
longer be realized through tuples like `{:early_termination, 0}`, instead
the violating binary will be returned as-is
- `ErrorCode`
- `name/1` returns `{:ok, String.t()} | :error` instead of
`String.t() | no_return()`
- `error_code/1` returns `{:ok, non_neg_integer()} | :error` instead of
`non_neg_integer() | no_return()`
- `Event`
- `deserialize/1` returns `{:error, bin}` instead of
`{:error, {:bad_event_code, bin}}`
- `HCI`
- `deserialize/1` the binary returned in an error tuple like
`{:error, binary()}` is now always the binary given to the function instead
of a subbinary
- `InquiryComplete`
- `serialize/1` may now return `{:error, InquiryComplete.t()}` instead of
crashing when failing to serialize the event's status
- `ManufacturerData`
- namespace changed from `Harald` to `Harald.DataType`, modules previously
namespaced under `Harald.ManufacturerData` are impacted
- `deserialize/1` returns `{:error, bin}` instead of
`{:error, {:unhandled_company_id, bin}}`
- `ManufacturerDataBehaviour`
- renamed to `Harald.DataType.ManufacturerDataBehaviour`
- `Hci`
- `t::command/0` updated
- `opcode/2` no longer guards its parameters
- `command/{1,2}`'s return is prefixed with the command packet indicator
`<<1>>`
- `ControllerAndBaseband`
- `read_local_name/0`'s return is prefixed with a command packet indicator
- `LeController`
- `set_enable_scan/2`'s return is prefixed with a command packet indicator
- `set_scan_parameters/1`'s return is prefixed with a command packet
indicator
- `Le`
- `scan/{1,2}`'s return is now wrapped in an ok tuple
- `Transport`
- `t::command/0` removed
- `send_command/2` renamed to `send_binary/2`
- `TransportAdapter`
- `UART` `send_command/2` renamed to `send_binary/2`
- `send_binary/2` no longer prepends its `bin`s with `<<1>>`

## Bugfixes

- `Transport`
- errors during deserialization no longer prevent a Bluetooth event from being
dispatched to handlers
- `ManufacturerData`
- `deserialize/1` asserts the company identifier to be 2 bytes

## Enhancements

- `DataType`
- add module
- `Serializable`
- add `assert_symmetry/2`
- `ServiceData`
- add module
- `Transport`
- `LE` is always included as a handler WRT `Transport`
- documentation improvements
- `Hci`
- `t::event/0` added
- `Le`
- `scan/{1,2}` no longer crashes when the transport times out
- `Packet`
- `type/1` added to return the associated packet indicator
- `Transport`
- `start_link/1`
- `:handle_start` option added, a callback once the transport starts. The
callback shall return a list of binaries that will be sent to the
Bluetooth Controller before anything else
- fails explicitly when a namespace is not provided
- `add_handler/2` added
- `t::handler_msg/0` added
- `t::handle_start/0` added
- `t::handle_start_ret/0` added
- `t::handlers/0` added
4 changes: 1 addition & 3 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ if Mix.env() == :dev do
clear: true,
tasks: [
"test --stale",
"format --check-formatted",
"dialyzer --halt-exit-status",
"credo -A"
"format --check-formatted"
]
end
4 changes: 1 addition & 3 deletions dialyzer_ignore.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
[
~r/test\/support\/generators.*return/
]
[]
2 changes: 2 additions & 0 deletions lib/harald.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule Harald do
@moduledoc """
An Elixir Bluetooth Host library.
High level Bluetooth functionality.
"""
end
2 changes: 1 addition & 1 deletion lib/harald/data_type/manufacturer_behaviour.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Harald.ManufacturerDataBehaviour do
defmodule Harald.DataType.ManufacturerDataBehaviour do
@moduledoc """
Defines a behaviour that manufacturer data modules should implement.
"""
Expand Down
6 changes: 5 additions & 1 deletion lib/harald/data_type/manufacturer_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ defmodule Harald.DataType.ManufacturerData do

@doc """
Deserializes a manufacturer data binary.
iex> deserialize(<<76, 0, 2, 21, 172, 185, 137, 206, 253, 163, 76, 179, 137, 41, 101, 34, 252, 127, 2, 42, 181, 255, 224, 255, 225>>)
{:ok, {"Apple, Inc.", {"iBeacon", %{major: 46591, minor: 57599, tx_power: 225, uuid: 229590585283448776073135497520678371882}}}}
"""
def deserialize(binary)

Enum.each(@modules, fn
module ->
def deserialize(
<<unquote(CompanyIdentifiers.id(module.company()))::little, sub_bin::binary>> = bin
<<unquote(CompanyIdentifiers.id(module.company()))::little-size(16), sub_bin::binary>> =
bin
) do
case unquote(module).deserialize(sub_bin) do
{:ok, data} -> {:ok, {unquote(module).company, data}}
Expand Down
2 changes: 1 addition & 1 deletion lib/harald/data_type/manufacturer_data/apple.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Harald.DataType.ManufacturerData.Apple do
Reference: https://en.wikipedia.org/wiki/IBeacon#Packet_Structure_Byte_Map
"""

alias Harald.{ManufacturerDataBehaviour, Serializable}
alias Harald.{DataType.ManufacturerDataBehaviour, Serializable}

@behaviour ManufacturerDataBehaviour

Expand Down
16 changes: 8 additions & 8 deletions lib/harald/hci.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ defmodule Harald.HCI do

@behaviour Serializable

@type command :: binary()
@type event :: binary()

@typedoc """
OpCode Group Field.
Expand Down Expand Up @@ -40,26 +43,23 @@ defmodule Harald.HCI do

@type opts :: binary() | [opt()]

@type command :: <<_::8, _::_*8>>

@spec opcode(ogf(), ocf()) :: opcode()
def opcode(ogf, ocf) when ogf < 64 and ocf < 1024 do
def opcode(ogf, ocf) do
<<opcode::size(16)>> = <<ogf::size(6), ocf::size(10)>>
<<opcode::little-size(16)>>
end

@spec command(opcode(), opts()) :: command()
def command(opcode, opts \\ "")
def command(opcode, opts \\ <<>>)

def command(opcode, [_ | _] = opts) do
opts_bin = for o <- opts, into: "", do: to_bin(o)

opts_bin = for o <- opts, into: <<>>, do: to_bin(o)
command(opcode, opts_bin)
end

def command(opcode, opts) do
s = byte_size(opts)
opcode <> <<s::size(8)>> <> opts
size = byte_size(opts)
<<1, opcode::binary, size, opts::binary>>
end

@doc """
Expand Down
12 changes: 0 additions & 12 deletions lib/harald/hci/arrayed_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -169,18 +169,6 @@ defmodule Harald.HCI.ArrayedData do
deserialize(schema, bin, %{state | index: index + 1, variable: variable})
end

# pull data off the binary - reading variable lengths
defp deserialize(
schema,
bin,
%{field_size: {:variable, _, field_size}, index: index, length: length} = state
)
when index <= length do
<<parameter::little-size(field_size), bin::binary>> = bin
variable = Map.update!(state.variable, state.field, &[parameter | &1])
deserialize(schema, bin, %{state | index: index + 1, variable: variable})
end

# pull data off the binary - reading variable length targets
defp deserialize(schema, bin, %{field_size: variable_key, index: index, length: length} = state)
when index <= length and is_atom(variable_key) do
Expand Down
6 changes: 3 additions & 3 deletions lib/harald/hci/controller_and_baseband.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
defmodule Harald.HCI.ControllerAndBaseband do
alias Harald.HCI

@moduledoc """
HCI commands for working with the controller and baseband.
Expand All @@ -12,14 +10,16 @@ defmodule Harald.HCI.ControllerAndBaseband do
Bluetooth Spec v5
"""

alias Harald.HCI

@ogf 0x03

@doc """
> The Read_Local_Name command provides the ability to read the stored user-friendly name for
> the BR/EDR Controller. See Section 6.23.
iex> read_local_name()
<<20, 12, 0>>
<<1, 20, 12, 0>>
"""
@spec read_local_name :: HCI.command()
def read_local_name, do: @ogf |> HCI.opcode(0x0014) |> HCI.command()
Expand Down
8 changes: 4 additions & 4 deletions lib/harald/hci/le_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ defmodule Harald.HCI.LEController do
Reference: Version 5.0, Vol 2, Part E, 7.8.11
iex> set_enable_scan(true)
<<12, 32, 2, 1, 0>>
<<1, 12, 32, 2, 1, 0>>
iex> set_enable_scan(false)
<<12, 32, 2, 0, 0>>
<<1, 12, 32, 2, 0, 0>>
"""
@spec set_enable_scan(HCI.opt(), HCI.opt()) :: HCI.command()
def set_enable_scan(enable, filter_duplicates \\ false) do
Expand All @@ -60,7 +60,7 @@ defmodule Harald.HCI.LEController do
Reference: Version 5.0, Vol 2, Part E, 7.8.10
iex> set_scan_parameters(le_scan_type: 0x01)
<<11, 32, 7, 1, 16, 0, 16, 0, 0, 0>>
<<1, 11, 32, 7, 1, 16, 0, 16, 0, 0, 0>>
iex> set_scan_parameters(
iex> le_scan_type: 0x01,
Expand All @@ -69,7 +69,7 @@ defmodule Harald.HCI.LEController do
iex> own_address_type: 0x01,
iex> scanning_filter_policy: 0x01
iex> )
<<11, 32, 7, 1, 4, 0, 4, 0, 1, 1>>
<<1, 11, 32, 7, 1, 4, 0, 4, 0, 1, 1>>
"""
@spec set_scan_parameters(keyword) :: HCI.command()
def set_scan_parameters(new_params) do
Expand Down
12 changes: 8 additions & 4 deletions lib/harald/hci/packet.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ defmodule Harald.HCI.Packet do
Reference: Version 5.0, Vol 4, Part A, 2
"""

@type_command 0x01
@type_event 0x04

@doc """
Returns a keyword list definition of HCI packet types.
"""
def types do
[
# command: 0x01,
# acl_data: 0x02,
# synchronous_data: 0x03,
event: 0x04
# command: @type_command
event: @type_event
]
end

def type(:command), do: @type_command
def type(:event), do: @type_event
end
30 changes: 16 additions & 14 deletions lib/harald/le.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ defmodule Harald.LE do
end

def scan(namespace, opts \\ []) do
opts =
[
time: 5_000
]
|> Keyword.merge(opts)

GenServer.call(name(namespace), {:scan, namespace, opts[:time]}, opts[:time] + 1000)
opts = Keyword.merge([time: 5_000], opts)
ret = GenServer.call(name(namespace), {:scan, namespace, opts[:time]}, opts[:time] + 1000)
{:ok, ret}
end

def name(namespace), do: String.to_atom("#{namespace}.#{__MODULE__}")
Expand All @@ -38,6 +34,15 @@ defmodule Harald.LE do
{:ok, %State{}}
end

@impl GenServer
def handle_call({:scan, ns, timeout}, from, state) do
:ok = Transport.send_binary(ns, LEController.set_enable_scan(true, true))
Process.send_after(self(), {:stop_scan, ns, from}, timeout)
{:noreply, state}
catch
:exit, {:timeout, _} -> {:reply, {:error, :timeout}, state}
end

@impl GenServer
def handle_info(
{:bluetooth_event, %LEMeta{subevent: %AdvertisingReport{devices: devices}}},
Expand All @@ -57,17 +62,14 @@ defmodule Harald.LE do
end

def handle_info({:stop_scan, ns, from}, %State{devices: devices}) do
:ok = Transport.send_command(ns, LEController.set_enable_scan(false))
:ok = Transport.send_binary(ns, LEController.set_enable_scan(false))
GenServer.reply(from, devices)
{:noreply, %State{}}
end

@impl GenServer
def handle_call({:scan, ns, timeout}, from, state) do
:ok = Transport.send_command(ns, LEController.set_enable_scan(true, true))
Process.send_after(self(), {:stop_scan, ns, from}, timeout)
{:noreply, state}
end
# this catchs the reply from the transport if the try/catch above for a :scan was triggered
# by a timeout
def handle_info({ref, :ok}, state) when is_reference(ref), do: {:noreply, state}

defp put_device(address, device_report, %State{devices: devices} = state) do
%State{state | devices: Map.put(devices, address, device_report)}
Expand Down
Loading

0 comments on commit 6afbb93

Please sign in to comment.