Question about AdvertisingReport events not received by the process.
Closed this issue ยท 2 comments
Hello ๐ ,
First of all, thanks for your awesome work, the examples help me to understand how your lib works.
I have a small project where I retrieve data from a hydrometer that emits its data through BLE. I've used the scanner example , adapted to my usage, but my GenServer didn't received any AdvertisingReport
event and I had the following warning message in the log: BLE: Unknown HCI frame ...
.
After multiple explorations through the codebase, I've edited the handle_hci_packet
function in lib/blue_heron/hci/transport.ex
:
defp handle_hci_packet(packet, data) do
case deserialize(packet) do
%{status: 0} = reply ->
for pid <- data.handlers, do: send(pid, {:HCI_EVENT_PACKET, reply})
{:ok, reply, data}
%{return_parameters: %{status: 0}} = reply ->
for pid <- data.handlers, do: send(pid, {:HCI_EVENT_PACKET, reply})
{:ok, reply, data}
%{code: 0x13} = reply ->
# Handle HCI.Event.NumberOfCompletedPackets
for pid <- data.handlers, do: send(pid, {:HCI_EVENT_PACKET, reply})
{:ok, reply, data}
%{code: 62, subevent_code: 5} = reply ->
# Handle HCI.Event.LEMeta.LongTermKeyRequest
for pid <- data.handlers, do: send(pid, {:HCI_EVENT_PACKET, reply})
{:ok, reply, data}
%{code: 62, subevent_code: 3} = reply ->
# handle HCI.Event.LEMeta.ConnectionUpdateComplete
for pid <- data.handlers, do: send(pid, {:HCI_EVENT_PACKET, reply})
{:ok, reply, data}
%{code: 62, num_reports: _} = reply ->
# handle HCI.Event.LEMeta.AdvertisingReport
for pid <- data.handlers, do: send(pid, {:HCI_EVENT_PACKET, reply})
{:ok, reply, data}
%{opcode: _opcode} = reply ->
{:error, reply, data}
%{} = reply ->
Logger.warning("BLE: Unknown HCI frame #{inspect(reply)}")
{:error, reply, data}
{:error, unknown} ->
{:error, unknown, data}
end
end
Before proposing a pull request, I was wondering if I did something wrong in my code and if I misunderstood how the library works.
Here's the code (I haven't cleaned it up yet):
defmodule BlueHeronScan do
use GenServer
require Logger
alias BlueHeron.HCI.Command.{
ControllerAndBaseband.WriteLocalName,
LEController.SetScanEnable
}
alias BlueHeron.HCI.Event.{
LEMeta.AdvertisingReport,
LEMeta.AdvertisingReport.Device
}
alias BlueHeron.DataType.ManufacturerData.Apple
@init_commands [%WriteLocalName{name: "TiltHydrometerScan"}]
@default_uart_config %{
device: "ttyACM0",
uart_opts: [speed: 115_200],
init_commands: @init_commands
}
@default_usb_config %{
vid: 0x0BDA,
pid: 0xB82C,
init_commands: @init_commands
}
@tilt_hydrometer_ids %{
# A495BB10C5B14B44B5121370F02D74DE
218_770_837_680_077_257_813_263_174_056_301_917_406 => "red",
# A495BB20C5B14B44B5121370F02D74DE
218_770_838_947_727_858_041_492_575_553_005_122_782 => "green",
# A495BB30C5B14B44B5121370F02D74DE
218_770_840_215_378_458_269_721_977_049_708_328_158 => "black",
# A495BB40C5B14B44B5121370F02D74DE
218_770_841_483_029_058_497_951_378_546_411_533_534 => "purple",
# A495BB50C5B14B44B5121370F02D74DE
218_770_842_750_679_658_726_180_780_043_114_738_910 => "orange",
# A495BB60C5B14B44B5121370F02D74DE
218_770_844_018_330_258_954_410_181_539_817_944_286 => "blue",
# A495BB70C5B14B44B5121370F02D74DE
218_770_845_285_980_859_182_639_583_036_521_149_662 => "yellow",
# A495BB80C5B14B44B5121370F02D74DE
218_770_846_553_631_459_410_868_984_533_224_355_038 => "pink"
}
def start_link(transport_type, config \\ %{})
def start_link(:uart, config) do
config = struct(BlueHeronTransportUART, Map.merge(@default_uart_config, config))
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
def start_link(:usb, config) do
config = struct(BlueHeronTransportUSB, Map.merge(@default_usb_config, config))
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
def enable(pid) do
GenServer.call(pid, :scan_enable)
end
@doc """
Disable BLE scanning.
"""
def disable(pid) do
send(pid, :scan_disable)
end
def devices(pid) do
GenServer.call(pid, :devices)
end
@impl GenServer
def init(config) do
# Create a context for BlueHeron to operate with.
{:ok, ctx} = BlueHeron.transport(config)
# Subscribe to HCI and ACL events.
BlueHeron.add_event_handler(ctx)
{:ok, %{ctx: ctx, working: false, devices: %{}}}
end
@impl GenServer
def handle_info({:BLUETOOTH_EVENT_STATE, :HCI_STATE_WORKING}, state) do
# Enable BLE Scanning. This will deliver messages to the process mailbox
# when other devices broadcast.
state = %{state | working: true}
scan(state, true)
{:noreply, state}
end
# Scan AdvertisingReport packets.
@impl GenServer
def handle_info(
{:HCI_EVENT_PACKET, %AdvertisingReport{devices: devices} = event},
state
) do
{:noreply, Enum.reduce(devices, state, &scan_device/2)}
end
# Ignore other HCI Events.
@impl GenServer
def handle_info({:HCI_EVENT_PACKET, _val}, state) do
# Logger.debug("#{__MODULE__} ignore HCI Event #{inspect(val)}")
{:noreply, state}
end
def handle_info(:scan_disable, state) do
{:noreply, state}
end
@impl GenServer
def handle_call(:devices, _from, state) do
{:reply, {:ok, state.devices}, state}
end
def handle_call(:scan_enable, _from, state) do
{:reply, scan(state, true), state}
end
defp scan(%{working: false}, _enable) do
{:error, :not_working}
end
defp scan(%{ctx: ctx = %BlueHeron.Context{}}, enable) do
BlueHeron.hci_command(ctx, %SetScanEnable{le_scan_enable: enable})
status = if(enable, do: "enabled", else: "disabled")
Logger.info("#{__MODULE__} #{status} scanning")
end
defp scan_device(device, state) do
case device do
%Device{address: addr, data: data, rss: rssi} ->
Enum.reduce(data, state, fn e, acc ->
case parse_tilt_hydrometer(e) do
{:ok, tilt} -> Map.put(acc, :devices, Map.put(tilt, :rssi, rssi))
_ -> acc
end
end)
_ ->
state
end
end
defp parse_tilt_hydrometer({"Manufacturer Specific Data", data}) do
<<_::little-16, sdata::binary>> = data
case Apple.deserialize(sdata) do
{:ok, {"iBeacon", tilt}} -> Map.fetch(@tilt_hydrometer_ids, tilt.uuid) |> dbg
{:error, _} -> 1
end
with {:ok, {"iBeacon", tilt}} <- Apple.deserialize(sdata),
{:ok, color} <- Map.fetch(@tilt_hydrometer_ids, tilt.uuid) do
dbg(color)
{:ok,
%{
# farenheit
temperature: tilt.major,
gravity: tilt.minor / 1000,
color: color,
tx_power: tilt.tx_power
}}
end
end
defp parse_tilt_hydrometer(_) do
{:error}
end
end
Did I miss something?
Hello ๐
Thank you for filing a very detailed issue. It looks like the issue was inadvertently introduced a while back. I will happily review a PR that introduces the missing case clause for HCI.Event.LEMeta.AdvertisingReport
, so feel free to submit it.