GithubHelp home page GithubHelp logo

isabella232 / ebus-gatt Goto Github PK

View Code? Open in Web Editor NEW

This project forked from helium/ebus-gatt

0.0 0.0 0.0 721 KB

A bluetooth gatt server implementation using ebus

License: Apache License 2.0

Makefile 0.79% Erlang 99.21%

ebus-gatt's Introduction

Erlang BLE GATT

The ebus-gatt library offers facilities for standing up an Erlang BLE GATT server on an embedded Linux system. Onboarding a connected consumer device typically requires sending down a home Wi-Fi SSID and passphrase. Nowadays, users expect to be able to onboard a new device from a mobile app running on their smartphone. Running a BLE GATT server on the target device makes that mobile app style of onboarding possible over Bluetooth.

Features

Issue BLE advertisements so smartphones can discover and pair with a device.

Construct a GATT tree with all of the following types of objects.

This GATT tree defines the BLE application layer (signals and slots) that a mobile app uses to communicate with a device.

Installation

Since ebus-gatt is an Erlang/OTP library we recommend that you develop your BLE GATT server with a recent release (OTP 21.1 or later) of Erlang. To install the latest stable Erlang release on Ubuntu or some other Debian-based Linux distro see Erlang Solutions' instructions under "Installation using repository".

Add gatt to the deps section of your rebar.config:

{deps, [
        {gatt, ".*", {git, "https://github.com/helium/ebus-gatt", {branch, "master"}}}
       ]}

Adding ebus-gatt to an Erlang/OTP application this way requires that the library be built using rebar3. Since ebus-gatt depends on ebus that means rebar3 has to build ebus as well. Install the following dependencies on Ubuntu 18.04 so rebar3 can build ebus-gatt:

$ sudo apt install make gcc libdbus-1-dev

To ensure that gatt is started when your application starts also add it to the applications section of your application's .app.src file. For example:

{application, my_app,
 [{description, "An application using gatt"},
  {vsn, "git"},
  {registered, []},
  {applications,
   [kernel,
    stdlib,
    gatt
   ]},
  {env,[]}
 ]}.

D-Bus and BlueZ also need to be installed and running on the target device for ebus-gatt to work. Since running a GATT server on Linux is still somewhat uncommon make sure you have version 5.45 or later of BlueZ installed:

$ bluetoothd -v
5.48

To restart BlueZ on a Linux distro that uses systemd for its init system:

$ sudo service bluetooth restart

Usage

Design patterns for the various types of GATT tree objects are defined by ebus-gatt as OTP behaviors. You construct a concrete GATT server application in Erlang by implementing these behaviors. See gateway-config for an example of how to structure an Erlang/OTP application based on ebus-gatt. There you will find modules that implement the ble_advertisement, gatt_application, gatt_service and gatt_characteristic behaviors.

Supervision

At the root of gateway_config's GATT tree is a gatt_application also known as a "profile". An OTP supervisor starts this gatt_application and restarts it in the event that it crashes. See gateway_config_app.erl and gateway_config_sup.erl for how to start and configure an OTP supervisor for our example_gatt_application:

ChildSpecs = [
              #{
                id => example_gatt_application,
                restart => permanent,
                type => supervisor,
                start => {gatt_application_sup, start_link, [example_gatt_application, []]}
               }
             ]

Advertising

Once an example_ble_advertisement and example_gatt_application are implemented and the example_gatt_application is started you can begin advertising so that a nearby mobile phone can discover and pair with it:

{ok, Bus} = example_gatt_application:bus(),
{ok, AdvPid} = ble_advertisement:start_link(Bus, example_gatt_application:path(), 0,
                                            example_ble_advertisement, []).

Once a connection is established with a generic BLE mobile app like nRF Connect you can then stop advertising:

ble_advertisement:stop(AdvPid, normal).

Applications

A gatt_application is a collection of one or more gatt_service[s]. The following example_gatt_application module only has only one gatt_service named example_gatt_service:

-module(example_gatt_application).

-behavior(gatt_application).

-export([bus/0, adapter_path/0, path/0, init/1]).

-record(state, {}).

init([]) ->
    DeviceInfo = #{
                   manufacturer_name => <<"Acme">>,
                   firmware_revision => <<"1.2.3">>,
                   serial_number => <<"A7654321">>
                  },
    Services = [
                {example_gatt_service, 0, true},
                {gatt_service_device_information, 1, true, [DeviceInfo]}
               ],
    {ok, Services, #state{}}.

bus() ->
    ebus:system().

adapter_path() ->
    %% NOTE: The Bluetooth adapter path for your device may vary
    "/org/bluez/hci0".

path() ->
    "/com/acme/onboard".

Note that D-Bus requires a .conf file so that other local D-Bus services like connmand can communicate with this GATT server. The .conf file in this example should be named com.acme.Onboard.conf based on the object path() above and placed somewhere like /etc/dbus-1/system.d depending on your Linux distro. The contents of com.acme.Onboard.conf should be:

<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy user="root">
    <allow own="com.acme.Onboard"/>
    <allow send_interface="com.acme.Onboard"/>
  </policy>
  <policy context="default">
     <allow send_destination="com.acme.Onboard"/>
  </policy>
</busconfig>

Services

A gatt_service is comprised of of one or more gatt_characteristics[s]. The following example_gatt_service module has three gatt_characteristics[s] named example_gatt_char_wifi_connect, example_gatt_char_wifi_ssid and example_gatt_char_wifi_passphrase respectively:

-module(example_gatt_service).
-include("example_gatt.hrl").

-behavior(gatt_service).

-export([init/1, uuid/0, handle_info/2]).

-record(state, {
                connect_result_char=undefined :: undefined | ebus:object_path()
               }).

uuid() ->
    ?UUID_EXAMPLE_GATT_SERVICE.

init(_) ->
    Characteristics =
        [
         {example_gatt_char_wifi_connect, 1, []},
         {example_gatt_char_wifi_ssid, 2, []},
         {example_gatt_char_wifi_passphrase, 3, []}
        ],
    {ok, Characteristics, #state{}}.

handle_info({connect, _Ssid, _Passphrase}=Msg, State=#state{}) ->
    %% TODO: Attempt to connect to given Wi-Fi SSID with given passphrase
    {noreply, State}

GATT services and their characteristics must have distinct UUIDs. Define all the UUIDs for a gatt_service inside a common Erlang header file like example_gatt.hrl for convenience:

-define(UUID_EXAMPLE_GATT_SERVICE, "b027ee62-ee7f-4048-b912-b5e49cab4839").
-define(UUID_EXAMPLE_GATT_CHAR_WIFI_CONNECT, "fbc273af-590d-4885-b8e2-9a58adda69bf").
-define(UUID_EXAMPLE_GATT_CHAR_WIFI_SSID, "7f797fe1-9b61-4cc1-80f1-b03add5a0c92").
-define(UUID_EXAMPLE_GATT_CHAR_WIFI_PASSPHRASE, "c0fce586-fc9c-468c-99f1-1713285096e0").

OTP behaviors are like inheritance in an object-oriented programming language. If you trace the lineage of gatt_service you will find that every gatt_service is an ebus_object and every ebus_object is a gen_server. The gen_server behavior defines a handle_info/2 callback to deal with messages sent directly to the example_gatt_service with the ! operator. That includes messages like self ! {connect, Ssid, Passphrase} which originate from the gen_server itself.

The above example_gatt_service includes a stub for an unimplemented handle_info({connect, Ssid, Passphrase}, State) callback. To actually connect when that callback is invoked you need some way to pass the incoming Ssid and Passphrase down to your device's Wi-Fi network interface. Consider adding connman to your rebar.config if you want to use connmand to negotiate your device's Wi-Fi connection. If your device is running Nerves check out nerves_network.

Characteristics

Each gatt_service is comprised of gatt_characteristic[s] with optional "descriptors" attached to them. Think of gatt_characteristic[s] as value slots that a BLE client can read from, write to and subscribe to. Here is an example of a very simple gatt_characteristic that supports reading from and writing to a utf8_string value.

-module(example_gatt_char_ssid).
-include("example_gatt.hrl").

-behavior(gatt_characteristic).

-export([init/2,
         uuid/1,
         flags/1,
         read_value/2,
         write_value/2]).

-record(state, {path :: ebus:object_path()}).

uuid(_) ->
    ?UUID_EXAMPLE_GATT_CHAR_WIFI_SSID.

flags(_) ->
    [read, write].

init(Path, _) ->
    Descriptors =
        [
         {gatt_descriptor_cud, 0, ["Wi-Fi SSID"]},
         {gatt_descriptor_pf, 1, [utf8_string]}
        ],
    {ok, Descriptors, #state{path=Path}}.

read_value(State=#state{}, _) ->
    {ok, State#state.value, State}.

write_value(State=#state{}, Bin) ->
    {ok, State#state{value=Bin}}.

Sometimes you don't want an unknown BLE client to be able to read the value of a gatt_characteristic. A cleartext Wi-Fi passphrase characteristic should be write-only for example. You can define the flags(_) for that characteristic as [read] instead of [read, write] to protect it from hackers.

There is also a notify flag to enable push notifications of value changes to connected BLE clients. This is useful for characteristics that represent status changes like offline -> connecting -> online. The nRF Connect mobile app has support for these push notifications and is a great way to explore GATT services and peek and poke at GATT characteristics.

ebus-gatt's People

Contributors

fvasquez avatar madninja avatar

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.