Giter Club home page Giter Club logo

blue_heron's People

Contributors

amclain avatar angrycandy avatar axelson avatar connorrigby avatar fhunleth avatar jjcarstens avatar kevinansfield avatar markushutzler avatar petermm avatar trarbr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blue_heron's Issues

Implementing a Beacon in RPi3A+

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 size

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

Using MacBooks included blueooth

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?

Simplify encoding/decoding of HCI packets

#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.

Failed to write to hcidump.pklg - :enospc

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

Upload firmware to module

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.

RPi 4

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

Unable to successfully run on rpi3

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
Full log
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

Easier way to switch between path and hex deps

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.

Implement GATT

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 MTU doesn't check size

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

Alternate method of getting LibUSB for Nerves

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.

Question about AdvertisingReport events not received by the process.

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?

Unexpected transport data binary shape on rpi3

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.

Does BlueHeron.Context need to exist?

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.

ATT Client and HCI transport timeouts

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.

SetAdvertisingData ArgumentError

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.