blue-heron / blue_heron Goto Github PK
View Code? Open in Web Editor NEWUse Bluetooth LE in Elixir
License: Apache License 2.0
Use Bluetooth LE in Elixir
License: Apache License 2.0
I'm building a simple Beacon emitter (in Nerves) for RPI 3A+ and I'm using {:blue_heron_transport_uart, "~> 0.1.3"}
and {:blue_heron, "~> 0.3.0"}
.
Not sure if I need to load any specific overlays onto the raspberry pi config though.
I get this error when initializing the Beacon server:
14:24:06.052 [info] Application my_device exited: MyDevice.Application.start(:normal, []) returned an error: shutdown: failed to start child: MyDevice.Bluetooth.Beacon
** (EXIT) an exception was raised:
** (MatchError) no match of right hand side value: {:error, :enoent}
(blue_heron_transport_uart 0.1.3) lib/blue_heron_transport_uart.ex:50: BlueHeronTransportUART.init/1
(stdlib 5.0) gen_server.erl:962: :gen_server.init_it/2
(stdlib 5.0) gen_server.erl:917: :gen_server.init_it/6
(stdlib 5.0) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
According to the README the BT chipset of the RPI3A+ is not supported yet due to #21 but it was opened a long time ago so I'm not sure where things stand here.
Could it be related?
These are my modules:
Here are the modules, I believe the error comes up from this line in the init/1
of the Beacon module:
{:ok, ctx} = BlueHeron.transport(transport_config)
defmodule MyDevice.Bluetooth.GattServer do
@behaviour BlueHeron.GATT.Server
require Logger
@server_name "Beacon Server"
@impl BlueHeron.GATT.Server
def profile() do
[
BlueHeron.GATT.Service.new(%{
id: :gap,
type: 0x1800,
characteristics: [
BlueHeron.GATT.Characteristic.new(%{
id: {:gap, :device_name},
type: 0x2A00, # This is the standard UUID for "Device Name" characteristic.
properties: 0b0000010 # Read-only property.
})
]
})
]
end
@impl BlueHeron.GATT.Server
def read({:gap, :device_name}), do: @server_name
@impl BlueHeron.GATT.Server
def write(_tuple, value) do
Logger.info("Received a write attempt with value: #{value}")
end
end
defmodule MyDevice.Bluetooth.Beacon do
use GenServer
alias BlueHeron.Peripheral
alias BlueHeron.DataType.ManufacturerData.Apple
alias BlueHeron.HCI.Command.ControllerAndBaseband.WriteLocalName
require Logger
@init_commands [%WriteLocalName{name: "Guille's Beacon"}]
@default_uart_config %{
device: "ttyACM0",
uart_opts: [speed: 115_200],
init_commands: @init_commands
}
# Config for iBeacon advertising parameters
@advertising_params %{
# Equivalent to 100ms
interval_min: 160,
# Equivalent to 100ms
interval_max: 160,
type: :connectable_undirected,
own_address_type: :public,
# All channels: 37, 38, and 39
channel_map: 7,
filter_policy: :allow_any
}
def start_link(_config) do
{:ok, config} = Application.fetch_env(:my_device, :bluetooth)
Logger.info(inspect(config))
GenServer.start_link(__MODULE__, config)
end
@impl GenServer
def init(config) do
# Initialize the GATT Server
gatt_server = MyDevice.Bluetooth.GattServer
transport_config = struct(BlueHeronTransportUART, @default_uart_config)
# Initialize transport context
{:ok, ctx} = BlueHeron.transport(transport_config)
# Initialize the BlueHeron.Peripheral
{:ok, peripheral_pid} = Peripheral.start_link(ctx, gatt_server)
# Serialize the iBeacon payload
payload =
Apple.serialize(
{"iBeacon",
%{
major: config[:major],
minor: config[:minor],
tx_power: config[:tx_power],
uuid: config[:uuid]
}}
)
# Set advertising info in Peripheral
Peripheral.set_advertising_parameters(peripheral_pid, @advertising_params)
Peripheral.set_advertising_data(peripheral_pid, payload)
# Schedule advertising
schedule_advertising()
{:ok, %{peripheral_pid: peripheral_pid, gatt_server: gatt_server, config: config}}
end
defp schedule_advertising() do
Process.send_after(self(), :start_advertising, 1_000)
end
@impl GenServer
def handle_info(:start_advertising, state) do
# Start advertising for 9 sec
Peripheral.start_advertising(state.peripheral_pid)
Process.send_after(self(), :stop_advertising, 9_000)
{:noreply, state}
end
def handle_info(:stop_advertising, state) do
# Stop advertising
Peripheral.stop_advertising(state.peripheral_pid)
# Schedule the next advertising
schedule_advertising()
{:noreply, state}
end
end
advertising data is only allowed to be 31 octets long, however there are no checks for this, notable in the SetAdvertisingData payload serializer, it automatically calculates the offset and padding required to get to 31 octets, but supplying a payload larger than 31 causes the padding to be negative meaning that payload fails.
See also: set scan response which allows for more than the initial 31 bytes
Hi, i tried to get running by using the integrated bluetooth chip in the Mac, but couldn't get it to work. Is it supported? If so, which transport do I have to use?
#19 Started this, however i'm not 100% happy with it either.
All of the pattern matches are so incredibly simple that worrying about an entire system of macros seems unnecessary to me.
The way ACL is implemented seemed like a decent base, but implementing return parameters
for the CommandComplete event kind of put a wrench in that plan. The ACL implementation also uses a nested data structure that isn't a perfect fit for HCI since it's always a flat data structure.
I have created a clean nerves project which is running the example of #57 . After around 4 hours of execution I got the following logs spammed in my console:
10:51:22.592 [info] Plant #1 - moisture: 30
10:51:32.577 [info] Plant #1 - fertility: 168
10:51:42.607 [info] Plant #1 - temperature: 25.1
10:51:53.588 [info] Plant #1 - lux: 249
10:52:02.579 [info] Plant #1 - moisture: 30
10:52:12.592 [info] Plant #1 - fertility: 165
10:52:22.600 [info] Plant #1 - temperature: 25.1
10:52:32.619 [info] Plant #1 - lux: 249
10:52:42.585 [info] Plant #1 - moisture: 30
10:52:45.081 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.083 [debug] HCI Packet in <<0x3E, 0xC, 0x2, 0x1, 0x4, 0x1, 0x38, 0xE3, 0x57, 0x3B, 0x5D, 0x65, 0x0, 0xB0>>
10:52:45.135 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.137 [debug] HCI Packet in <<0x3E, 0x2B, 0x2, 0x1, 0x2, 0x1, 0x4C, 0x7F, 0x0, 0x5A, 0x38, 0x53, 0x1F, 0x1E, 0xFF, 0x4C, 0x0, 0x7, 0x19, 0x1, 0xE, 0x20, 0x2B, 0x77, 0x8F, 0x1, 0x0, 0x5, 0x64, 0xCC, 0x48, 0x3A, 0x55, 0x3D, 0x4C, 0x7C, 0x65, 0x93, 0xCE, 0xAA, 0x22, 0x55, 0x92, 0xAB, 0xBE>>
10:52:45.146 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.149 [debug] HCI Packet in <<0x3E, 0x2B, 0x2, 0x1, 0x3, 0x1, 0xCB, 0x6B, 0xA7, 0xAC, 0x9B, 0x25, 0x1F, 0x1E, 0xFF, 0x6, 0x0, 0x1, 0x9, 0x20, 0x2, 0x1, 0x16, 0x59, 0xFD, 0xCE, 0x70, 0xC7, 0xF3, 0x8E, 0x12, 0x5, 0xD7, 0x9C, 0xF2, 0xED, 0xC2, 0xA7, 0xA5, 0xEB, 0xBA, 0x8, 0x57, 0x19, 0xBB>>
10:52:45.169 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.172 [debug] HCI Packet in <<0x3E, 0x2A, 0x2, 0x1, 0x0, 0x1, 0xE0, 0x39, 0x93, 0x4D, 0x45, 0x41, 0x1E, 0x2, 0x1, 0x2, 0x13, 0xFF, 0xFF, 0xFF, 0x32, 0x35, 0x61, 0x66, 0x61, 0x37, 0x38, 0x61, 0x66, 0x35, 0x38, 0x32, 0x63, 0x35, 0x33, 0x34, 0x2, 0xA, 0xF1, 0x3, 0x3, 0x56, 0x34, 0xD5>>
10:52:45.200 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.203 [debug] HCI Packet in <<0x3E, 0x14, 0x2, 0x1, 0x4, 0x1, 0xE0, 0x39, 0x93, 0x4D, 0x45, 0x41, 0x8, 0x7, 0x9, 0x53, 0x48, 0x49, 0x45, 0x4C, 0x44, 0xD5>>
10:52:45.235 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.238 [debug] HCI Packet in <<0x3E, 0x2B, 0x2, 0x1, 0x3, 0x1, 0xFB, 0x16, 0xC3, 0xC1, 0x46, 0x40, 0x1F, 0x1E, 0xFF, 0x6, 0x0, 0x1, 0x9, 0x20, 0x2, 0xAF, 0xB4, 0x68, 0x75, 0xB, 0xD, 0x98, 0x7B, 0x5A, 0xF3, 0x1A, 0x3F, 0x75, 0x52, 0x4A, 0x6, 0x31, 0xEE, 0xF8, 0x5E, 0x48, 0xF5, 0xB9, 0xBA>>
10:52:45.254 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.257 [debug] HCI Packet in <<0x3E, 0x1A, 0x2, 0x1, 0x0, 0x1, 0x7A, 0x8C, 0xD8, 0x4B, 0x2A, 0x63, 0xE, 0x2, 0x1, 0x6, 0xA, 0xFF, 0x4C, 0x0, 0x10, 0x5, 0x1, 0x1C, 0x19, 0x2D, 0x52, 0xBB>>
10:52:45.274 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.277 [debug] HCI Packet in <<0x3E, 0x1D, 0x2, 0x1, 0x0, 0x1, 0x5D, 0x15, 0xDD, 0xE9, 0x8, 0x45, 0x11, 0x2, 0x1, 0x1A, 0x2, 0xA, 0x18, 0xA, 0xFF, 0x4C, 0x0, 0x10, 0x5, 0xD, 0x98, 0x88, 0xBE, 0xFE, 0xB6>>
10:52:45.297 [warn] Failed to write to hcidump.pklg - :enospc
10:52:45.299 [debug] HCI Packet in <<0x3E, 0x2B, 0x2, 0x1, 0x3, 0x1, 0x35, 0x90, 0x8F, 0x5D, 0x17, 0x30, 0x1F, 0x1E, 0xFF, 0x6, 0x0, 0x1, 0x9, 0x20, 0x2, 0xED, 0xAB, 0xCC, 0xE, 0xFD, 0x3A, 0x93, 0xD3, 0x30, 0x31, 0x6E, 0x31, 0x94, 0x25, 0x31, 0xB2, 0x78, 0x13, 0x6B, 0xCD, 0x10, 0x91, 0x19, 0xBB>>
... logs go on forever
What could the reason for this be? My df -h
output looks like this:
Filesystem Size Used Available Use% Mounted on
/dev/root 27.4M 27.4M 0 100% /
devtmpfs 1.0M 0 1.0M 0% /dev
tmpfs 30.9M 8.0K 30.9M 0% /tmp
tmpfs 15.5M 4.0K 15.5M 0% /run
/dev/mmcblk0p1 18.7M 5.9M 12.8M 32% /boot
/dev/mmcblk0p3 56.7G 148.0K 53.8G 0% /root
tmpfs 1.0M 0 1.0M 0% /sys/fs/cgroup
Currently, blue_heron
relies on the Bluetooth module having working firmware on boot or on side effects from desktop Linux kernel modules loading firmware. Since the goal is to work without assistance from the Linux kernel's Bluetooth stack, it needs to be able to upload firmware too.
Bluetooth module firmware seems to universally be distributed as .hcd
files. These files are effectively scripts containing HCI commands. Each HCI command should be sent to the module and blue_heron
should wait for an acknowledgment. It's probably fine to have multiple commands in flight to improve performance if that is an issue. Note that the C code has a limit of 4 commands in flight at a time. That could be changed.
Some more information is at https://community.cypress.com/community/software-forums/wiced-studio-bluetooth/wiced-studio-bluetooth-forums/blog/2019/07/10/cypress-bluetooth-soc-programming.
As you can see here, https://github.com/LibreELEC/brcmfmac_sdio-firmware-rpi/tree/master/firmware/brcm, .hcd
files are named based on the module. Somehow Linux figures out the name automatically. I assume that there's some HCI command or something that gives the name to use, but I don't know. I believe, but don't know for sure, that the other files from that link are completely handled by the WiFi driver and that we can ignore them for blue_heron
.
Hey,
not sure I understand the table on Readme page. Should blue_heron work on Raspberry pi 4 or not?
Thanks for the feedback.
Tomaz
This might be another incarnation of #38, or at least the failing data appears very similar, but I'm getting this error on commit 6b331ad which is the current main
. Here is the sasl report for the error that I see:
00:42:26.434 [error] Process #PID<0.2776.0> terminating
** (FunctionClauseError) no function clause matching in BlueHeron.ATT.Client.ready/3
(blue_heron 0.1.1) lib/blue_heron/att/client.ex:169: BlueHeron.ATT.Client.ready(:info, {:HCI_ACL_DATA_PACKET, %BlueHeron.ACL{data: %BlueHeron.L2Cap{cid: 4, data: %BlueHeron.ATT.ExchangeMTUResponse{opcode: 3, server_rx_mtu: 23}}, flags: %{bc: 0, pb: 0}, handle: 576}}, %BlueHeron.ATT.Client{attributes: [], caller: nil, client_mtu: 1961, connection: nil, connection_timer: nil, controlling_process: #PID<0.2770.0>, create_connection: nil, ctx: #BlueHeron.Context<0.2771.0>, server_mtu: nil, starting_handle: 1})
(stdlib 3.13) gen_statem.erl:1166: :gen_statem.loop_state_callback/11
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
00:42:25.811 [info] Init commands completed successfully
00:42:25.812 [info] Unknown packet for state CONNECT: %BlueHeron.HCI.Event.CommandComplete{
code: 0xE,
num_hci_command_packets: 0x1,
opcode: <<0x13, 0xC>>,
return_parameters: %{status: 0x0}
}
00:42:25.813 [info] Unknown packet for state CONNECT: %BlueHeron.HCI.Event.CommandComplete{
code: 0xE,
num_hci_command_packets: 0x1,
opcode: <<0x13, 0xC>>,
return_parameters: %{status: 0x0}
}
00:42:25.814 [info] Unknown packet for state CONNECT: %BlueHeron.HCI.Event.CommandComplete{
code: 0xE,
num_hci_command_packets: 0x1,
opcode: <<0x13, 0xC>>,
return_parameters: %{status: 0x0}
}
00:42:26.331 [info] Starting connection for H6001 Govee Bulb #Address<A4:C1:38:EC:49:BD>
00:42:26.332 [info] Opening connection: %BlueHeron.HCI.Command.LEController.CreateConnection{connection_interval_max: 24, connection_interval_min: 8, connection_latency: 4, initiator_filter_policy: 0, le_scan_interval: 96, le_scan_window: 48, max_ce_length: 48, min_ce_length: 2, ocf: 13, ogf: 8, opcode: "\r ", own_address_type: 0, peer_address: 181149790652861, peer_address_type: 0, supervision_timeout: 72}
00:42:26.342 [info] open connection success: {#PID<0.2770.0>, #Reference<0.912690843.805830659.151761>}
00:42:26.345 [info] Connection established
00:42:26.345 [info] Govee connection established for H6001 Govee Bulb A4:C1:38:EC:49:BD
00:42:26.420 [info] Server MTU: 23
00:42:26.434 [error] Process #PID<0.2776.0> terminating
** (FunctionClauseError) no function clause matching in BlueHeron.ATT.Client.ready/3
(blue_heron 0.1.1) lib/blue_heron/att/client.ex:169: BlueHeron.ATT.Client.ready(:info, {:HCI_ACL_DATA_PACKET, %BlueHeron.ACL{data: %BlueHeron.L2Cap{cid: 4, data: %BlueHeron.ATT.ExchangeMTUResponse{opcode: 3, server_rx_mtu: 23}}, flags: %{bc: 0, pb: 0}, handle: 576}}, %BlueHeron.ATT.Client{attributes: [], caller: nil, client_mtu: 1961, connection: nil, connection_timer: nil, controlling_process: #PID<0.2770.0>, create_connection: nil, ctx: #BlueHeron.Context<0.2771.0>, server_mtu: nil, starting_handle: 1})
(stdlib 3.13) gen_statem.erl:1166: :gen_statem.loop_state_callback/11
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Initial Call: BlueHeron.ATT.Client.init/1
Ancestors: [BLEServer, GoveePhx.Supervisor, #PID<0.2409.0>]
Message Queue Length: 0
Messages: []
Links: [#PID<0.2770.0>]
Dictionary: []
Trapping Exits: false
Status: :running
Heap Size: 2586
Stack Size: 28
Reductions: 23817
Neighbours:
#PID<0.2773.0>
Initial Call: Circuits.UART.init/1
Current Call: :gen_server.loop/7
Ancestors: [#PID<0.2772.0>, #PID<0.2771.0>, BLEServer, GoveePhx.Supervisor, #PID<0.2409.0>]
Message Queue Length: 0
Links: [#PID<0.2772.0>, #Port<0.93>]
Trapping Exits: false
Status: :waiting
Heap Size: 1598
Stack Size: 12
Reductions: 4839
Current Stacktrace:
(stdlib 3.13) gen_server.erl:437: :gen_server.loop/7
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
#PID<0.2772.0>
Initial Call: BlueHeronTransportUART.init/1
Current Call: :gen_server.loop/7
Ancestors: [#PID<0.2771.0>, BLEServer, GoveePhx.Supervisor, #PID<0.2409.0>]
Message Queue Length: 0
Links: [#PID<0.2771.0>, #PID<0.2773.0>]
Trapping Exits: false
Status: :waiting
Heap Size: 610
Stack Size: 12
Reductions: 1589
Current Stacktrace:
(stdlib 3.13) gen_server.erl:437: :gen_server.loop/7
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
#PID<0.2775.0>
Initial Call: BlueHeron.ATT.Client.init/1
Current Call: :gen_statem.loop_receive/3
Ancestors: [BLEServer, GoveePhx.Supervisor, #PID<0.2409.0>]
Message Queue Length: 0
Links: [#PID<0.2770.0>]
Trapping Exits: false
Status: :waiting
Heap Size: 2586
Stack Size: 11
Reductions: 26346
Current Stacktrace:
(stdlib 3.13) gen_statem.erl:1007: :gen_statem.loop_receive/3
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
#PID<0.2777.0>
Initial Call: BlueHeron.ATT.Client.init/1
Current Call: :erts_internal.await_result/1
Ancestors: [BLEServer, GoveePhx.Supervisor, #PID<0.2409.0>]
Message Queue Length: 0
Links: [#PID<0.2770.0>]
Trapping Exits: false
Status: :running
Heap Size: 1598
Stack Size: 35
Reductions: 24876
Current Stacktrace:
:erts_internal.await_result/1
(stdlib 3.13) proc_lib.erl:753: :proc_lib.proc_info/2
(stdlib 3.13) proc_lib.erl:725: :proc_lib.get_process_info/2
(stdlib 3.13) proc_lib.erl:659: :proc_lib.make_neighbour_report/1
(stdlib 3.13) proc_lib.erl:632: :proc_lib.make_neighbour_reports1/1
(stdlib 3.13) proc_lib.erl:640: :proc_lib.make_neighbour_reports1/1
(stdlib 3.13) proc_lib.erl:527: :proc_lib.crash_report/4
(stdlib 3.13) proc_lib.erl:246: :proc_lib.exit_p/3
#PID<0.2771.0>
Initial Call: BlueHeron.HCI.Transport.init/1
Current Call: :gen_statem.loop_receive/3
Ancestors: [BLEServer, GoveePhx.Supervisor, #PID<0.2409.0>]
Message Queue Length: 0
Links: [#PID<0.2770.0>, #PID<0.2772.0>]
Trapping Exits: false
Status: :waiting
Heap Size: 4185
Stack Size: 11
Reductions: 96433
Current Stacktrace:
(stdlib 3.13) gen_statem.erl:1007: :gen_statem.loop_receive/3
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
BLEServer (#PID<0.2770.0>)
Initial Call: Govee.BLEConnection.init/1
Current Call: :gen_server.loop/7
Ancestors: [GoveePhx.Supervisor, #PID<0.2409.0>]
Message Queue Length: 0
Links: [#PID<0.2771.0>, #PID<0.2776.0>, #PID<0.2777.0>, #PID<0.2775.0>, #PID<0.2410.0>]
Trapping Exits: false
Status: :waiting
Heap Size: 4185
Stack Size: 12
Reductions: 13996
Current Stacktrace:
(stdlib 3.13) gen_server.erl:437: :gen_server.loop/7
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
00:42:26.446 [error] Child Govee.BLEConnection of Supervisor GoveePhx.Supervisor terminated
** (exit) an exception was raised:
** (FunctionClauseError) no function clause matching in BlueHeron.ATT.Client.ready/3
(blue_heron 0.1.1) lib/blue_heron/att/client.ex:169: BlueHeron.ATT.Client.ready(:info, {:HCI_ACL_DATA_PACKET, %BlueHeron.ACL{data: %BlueHeron.L2Cap{cid: 4, data: %BlueHeron.ATT.ExchangeMTUResponse{opcode: 3, server_rx_mtu: 23}}, flags: %{bc: 0, pb: 0}, handle: 576}}, %BlueHeron.ATT.Client{attributes: [], caller: nil, client_mtu: 1961, connection: nil, connection_timer: nil, controlling_process: #PID<0.2770.0>, create_connection: nil, ctx: #BlueHeron.Context<0.2771.0>, server_mtu: nil, starting_handle: 1})
(stdlib 3.13) gen_statem.erl:1166: :gen_statem.loop_state_callback/11
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Pid: #PID<0.2770.0>
Start Call: Govee.BLEConnection.start_link([devices: [[type: :h6001, addr: 181149790652861], [type: :h6001, addr: 181149780509914], [type: :h6159, addr: 181149781888623]], transport_config: %BlueHeronTransportUART{device: "ttyAMA0", init_commands: [%BlueHeron.HCI.Command.ControllerAndBaseband.WriteLocalName{name: "Govee Controller", ocf: 19, ogf: 3, opcode: <<19, 12>>}], recv: nil, uart_opts: [speed: 115200], uart_pid: nil}])
Restart: :permanent
Shutdown: 5000
Type: :worker
00:42:26.447 [error] Child Govee.BLEConnection of Supervisor GoveePhx.Supervisor caused shutdown
** (exit) :reached_max_restart_intensity
Start Call: Govee.BLEConnection.start_link([devices: [[type: :h6001, addr: 181149790652861], [type: :h6001, addr: 181149780509914], [type: :h6159, addr: 181149781888623]], transport_config: %BlueHeronTransportUART{device: "ttyAMA0", init_commands: [%BlueHeron.HCI.Command.ControllerAndBaseband.WriteLocalName{name: "Govee Controller", ocf: 19, ogf: 3, opcode: <<19, 12>>}], recv: nil, uart_opts: [speed: 115200], uart_pid: nil}])
Restart: :permanent
Shutdown: 5000
Type: :worker
When working on BlueHeron, it would be nice to have the mix.exs
files in the project all use path dependencies. Of course, when publishing BlueHeron libraries on hex.pm, the path dependencies need to be converted to hex dependencies. Currently, I'm manually editing the mix.exs
files. If would be nice if I didn't have to do this.
The ATT Client currently decodes GATT data as raw uuids. Some of these UUIDs are "special" for the Generic
portion of this protocol.
ATT Client currently does an MTU exchange, but doesn't validate it. The bluetooth specification says that ACL packets must conform to the supplied MTU size
Right now the LibUSB adapter isn't available to Nerves users since LibUSB isn't enabled in any of our systems. I don't want to force
users to build their own custom system, and maintaining a separate custom system just for this project seems excessive as well.
LibUSB is a pretty simple project to compile, so it might be worth building it in place and static linking it to the libusb port code or
dumping the .so file in the priv
dir along with the port.
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?
While fixing #38 yesterday, i realized the USB transport isn't receiving ACL data at all. sending still works.
I suspect something in the C code is missing.
I was able to get the Govee bulb example working from a USB dongle on my PC, hooray! However it was still not working on my Raspberry Pi 3B despite the fact that I confirmed UART was at least able to scan and could clearly see the Govee bulb.
I kept getting this error
iex(2)> {:ok, pid} = GoveeBulb.start_link(:uart, %{device: "ttyAMA0"})
{:ok, #PID<0.1301.0>}
** (EXIT from #PID<0.1298.0>) shell process exited with reason: an exception was raised:
** (FunctionClauseError) no function clause matching in BlueHeron.ACL.deserialize/1
(blue_heron 0.1.0) lib/blue_heron/acl.ex:6: BlueHeron.ACL.deserialize(<<2, 64, 32, 7, 0, 3, 0, 4, 0, 3, 23, 0>>)
(blue_heron 0.1.0) lib/blue_heron/hci/transport.ex:266: BlueHeron.HCI.Transport.ready/3
(stdlib 3.13) gen_statem.erl:1166: :gen_statem.loop_state_callback/11
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
My guess based on this error on the rpi3, but everything working on USB, was that the rpi3 was just wrong about the 0x2
for some reason, and so transport data that was getting matched in def ready(:info, {:transport_data, <<0x4, hci::binary>>}, data) do
on my PC was getting caught in the offending stanza above. So, I duplicated the event packet stanza and used it to overshadow the ACL DATA packet stanza, leaving me with the following:
def ready(:info, {:transport_data, <<0x4, hci::binary>>}, data) do
Logger.hci_packet(:HCI_EVENT_PACKET, :in, hci)
case handle_hci_packet(hci, data) do
{:ok, %CommandComplete{} = reply, data} ->
actions = maybe_reply(data, reply)
{:keep_state, %{data | caller: nil}, actions}
{:ok, %CommandStatus{} = reply, data} ->
actions = maybe_reply(data, reply)
{:keep_state, %{data | caller: nil}, actions}
{:ok, _parsed, data} ->
{:keep_state, data, []}
{:error, _bin, data} ->
{:keep_state, data, []}
end
end
def ready(:info, {:transport_data, <<0x2, hci::binary>>}, data) do
Logger.hci_packet(:HCI_EVENT_PACKET, :in, hci)
case handle_hci_packet(hci, data) do
{:ok, %CommandComplete{} = reply, data} ->
actions = maybe_reply(data, reply)
{:keep_state, %{data | caller: nil}, actions}
{:ok, %CommandStatus{} = reply, data} ->
actions = maybe_reply(data, reply)
{:keep_state, %{data | caller: nil}, actions}
{:ok, _parsed, data} ->
{:keep_state, data, []}
{:error, _bin, data} ->
{:keep_state, data, []}
end
end
# def ready(:info, {:transport_data, <<0x2, acl::binary>>}, data) do
# Logger.hci_packet(:HCI_ACL_DATA_PACKET, :in, acl)
# acl = BlueHeron.ACL.deserialize(acl)
# for pid <- data.handlers, do: send(pid, {:HCI_ACL_DATA_PACKET, acl})
# :keep_state_and_data
# end
With this configuration, I was able to get it working on the rpi3 as expected, although obviously this is not ideal because I've stomped the ACL protocol by matching all 0x2
as if they are 0x4
.
Would love to take a crack at this if possible, although I'm guessing this is just something about the rpi3 itself? I really have no idea, but wanted to report because one other person confirmed this issue on their rpi3.
It gets quite excessive - see #57 which runs a scan 24/7 - resulting in hundreds of megabytes of logs per day..
so some kind of documented config, disabling the writes - currently I comment out the line manually:/ in
blue_heron/lib/blue_heron/hci_dump/logger.ex
Line 116 in 6b331ad
When i first laid out the API for BlueHeron
, i wasn't sure about how ATT, SecurityManager, etc would have worked, so i wanted to encapsulate the transport and whatever other higher level layers inside that one Context
. Now that ATT is implemented, i'm not 100% sure that is necessary.
It's currently possible to put the ATT Client into a state that makes it get stuck on a :gen_statem.call/3
. I think setting the third argument to a reasonable timeout (5000
is usually standard?) will fix it. It's possible that the HCI transport will need this as well.
Serializing SetAdvertisingData
fails on data lengths longer than a certain threshold.
15:45:54.764 [error] GenStateMachine #PID<0.1465.0> terminating
** (ArgumentError) argument error
(blue_heron 0.3.0) lib/blue_heron/hci/commands/le_controller/set_advertising_data.ex:11: BlueHeron.HCI.Serializable.BlueHeron.HCI.Command.LEController.SetAdvertisingData.serialize/1
(blue_heron 0.3.0) lib/blue_heron/hci/transport.ex:209: BlueHeron.HCI.Transport.ready/3
(stdlib 3.17) gen_statem.erl:1203: :gen_statem.loop_state_callback/11
(stdlib 3.17) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
State: {:ready, %BlueHeron.HCI.Transport{calls: %{}, config: %BlueHeronTransportUART{device: "ttyS3", init_commands: [], recv: nil, uart_opts: [speed: 3000000, framing: BlueHeronTransportUART.Framing, active: false, flow_control: :hardware], uart_pid: #PID<0.1464.0>}, errors: 0, handlers: [#PID<0.1467.0>], init_commands: [], max_error_count: 2, monitor: #Reference<0.3143178017.2729181185.138896>, pid: #PID<0.1466.0>}}
Callback mode: :state_functions
Postponed events: []
Last event: {{:call, {#PID<0.1467.0>, [:alias | #Reference<0.3143178017.2729246721.139217>]}}, {:send_command, %BlueHeron.HCI.Command.LEController.SetAdvertisingData{advertising_data: <<2, 1, 6, 11, 9, 101, 108, 105, 97, 115, 45, 102, 57, 48, 49, 17, 6, 192, 108, 209, 109, 104, 9, 223, 168, 92, 77, 12, 3, 189, 26, 163, 66>>, ocf: 8, ogf: 8, opcode: "\b "}}}
Issue seems to be related to padding. The number of zeros required comes up negative.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.