From e44813dddbf5ba063d29ae1e40862e7a7cbb6f43 Mon Sep 17 00:00:00 2001
From: Trygve Laugstøl <trygvis@inamo.no>
Date: Fri, 20 Feb 2015 22:56:22 +0100
Subject: Reorganizing the source code: o Moving main to apps/ o Moving the
 library sources to ble/ o Creating cmake files for each piece.

---
 Bluetooth.cpp               | 193 -----------------
 Bluetooth.h                 | 203 ------------------
 BluetoothImpl.h             | 147 -------------
 ByteBuffer.cpp              | 102 ---------
 ByteBuffer.h                |  91 --------
 CMakeLists.txt              |  56 +----
 LinuxBluetooth.cpp          | 512 --------------------------------------------
 apps/CMakeLists.txt         |  22 ++
 apps/ble-inspect-device.cpp |  72 +++++++
 apps/sm-get-value.cpp       |  75 +++++++
 apps/soil-moisture.h        | 118 ++++++++++
 ble/Bluetooth.cpp           | 193 +++++++++++++++++
 ble/Bluetooth.h             | 206 ++++++++++++++++++
 ble/BluetoothImpl.h         | 160 ++++++++++++++
 ble/ByteBuffer.cpp          | 102 +++++++++
 ble/ByteBuffer.h            |  91 ++++++++
 ble/CMakeLists.txt          |   3 +
 ble/LinuxBluetooth.cpp      | 512 ++++++++++++++++++++++++++++++++++++++++++++
 ble/log.h                   |  14 ++
 log.h                       |  14 --
 main.cpp                    |  72 -------
 test/CMakeLists.txt         |  30 +++
 22 files changed, 1601 insertions(+), 1387 deletions(-)
 delete mode 100644 Bluetooth.cpp
 delete mode 100644 Bluetooth.h
 delete mode 100644 BluetoothImpl.h
 delete mode 100644 ByteBuffer.cpp
 delete mode 100644 ByteBuffer.h
 delete mode 100644 LinuxBluetooth.cpp
 create mode 100644 apps/CMakeLists.txt
 create mode 100644 apps/ble-inspect-device.cpp
 create mode 100644 apps/sm-get-value.cpp
 create mode 100644 apps/soil-moisture.h
 create mode 100644 ble/Bluetooth.cpp
 create mode 100644 ble/Bluetooth.h
 create mode 100644 ble/BluetoothImpl.h
 create mode 100644 ble/ByteBuffer.cpp
 create mode 100644 ble/ByteBuffer.h
 create mode 100644 ble/CMakeLists.txt
 create mode 100644 ble/LinuxBluetooth.cpp
 create mode 100644 ble/log.h
 delete mode 100644 log.h
 delete mode 100644 main.cpp
 create mode 100644 test/CMakeLists.txt

diff --git a/Bluetooth.cpp b/Bluetooth.cpp
deleted file mode 100644
index b135282..0000000
--- a/Bluetooth.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-#include "Bluetooth.h"
-#include "BluetoothImpl.h"
-
-#include <sstream>
-#include <iomanip>
-#include <string.h>
-
-namespace trygvis {
-namespace bluetooth {
-using namespace std;
-
-// -----------------------------------------------------------------------
-// Mac
-// -----------------------------------------------------------------------
-
-string Mac::str() const {
-    std::ostringstream buf;
-
-    buf
-            << setw(2) << hex << setfill('0') << (int) bytes[0] << ":"
-            << setw(2) << hex << setfill('0') << (int) bytes[1] << ":"
-            << setw(2) << hex << setfill('0') << (int) bytes[2] << ":"
-            << setw(2) << hex << setfill('0') << (int) bytes[3] << ":"
-            << setw(2) << hex << setfill('0') << (int) bytes[4] << ":"
-            << setw(2) << hex << setfill('0') << (int) bytes[5];
-
-    return buf.str();
-}
-
-bool Mac::operator==(Mac &other) const {
-    const uint8_t *b = bytes;
-    return memcmp(b, other.bytes, sizeof(bytes)) == 0;
-}
-
-bool Mac::operator!=(Mac &other) const {
-    return !operator==(other);
-}
-
-bool operator<(const Mac &a, const Mac &b) {
-    return memcmp(a.bytes, b.bytes, sizeof(a.bytes)) < 0;
-}
-
-void Mac::copy(uint8_t &_0, uint8_t &_1, uint8_t &_2, uint8_t &_3, uint8_t &_4, uint8_t &_5) const {
-    _0 = bytes[0];
-    _1 = bytes[1];
-    _2 = bytes[2];
-    _3 = bytes[3];
-    _4 = bytes[4];
-    _5 = bytes[5];
-}
-
-Mac Mac::parseMac(string s) throw(BluetoothException) {
-    unsigned int bytes[6];
-    int count = sscanf(s.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x",
-            &bytes[0], &bytes[1], &bytes[2], &bytes[3], &bytes[4], &bytes[5]);
-
-    if (count != 6) {
-        throw BluetoothException("Unable to parse mac: " + s);
-    }
-
-    return Mac((uint8_t) bytes[0], (uint8_t) bytes[1], (uint8_t) bytes[2], (uint8_t) bytes[3], (uint8_t) bytes[4], (uint8_t) bytes[5]);
-}
-
-AttPdu::AttPdu(ByteBuffer &bytes) : bytes(bytes) {
-}
-
-AttPdu::AttPdu(ByteBuffer &bytes, AttPduType type) : bytes(bytes) {
-    bytes.write8(type);
-}
-
-AttPduType AttPdu::getType() {
-    return (AttPduType) bytes.get8(0);
-}
-
-void AttPdu::makeReadByGroupType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid) {
-    bytes.write8(AttPduType::READ_BY_GROUP_TYPE_REQ);
-    bytes.write16le(startHandle);
-    bytes.write16le(endHandle);
-    bytes.write16le(uuid.value);
-}
-
-void AttPdu::makeReadByType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid) {
-    bytes.write8(AttPduType::READ_BY_TYPE_REQ);
-    bytes.write16le(startHandle);
-    bytes.write16le(endHandle);
-    bytes.write16le(uuid.value);
-}
-
-vector<AttributeData> AttPdu::parse(ByteBuffer &bytes, AttPduType type) {
-    DF << "bytes: " << bytes.toString();
-
-    AttPduType t = (AttPduType) bytes.read8();
-
-    if (t == INVALID_HANDLE) {
-        return vector<AttributeData>();
-    }
-
-    if (t != type) {
-        throw BluetoothException("Unexpected type: " + to_string(t));
-    }
-
-    if (bytes.getSize() < 4) {
-        throw BluetoothException("Bad READ_BY_GROUP_TYPE_RES packet, expected at least 4 octets, got " + to_string(bytes.getSize()));
-    }
-
-    uint8_t length = bytes.read8();
-    D << "length=" << (int) length;
-
-    size_t count = (bytes.getSize() - 2) / length;
-    D << "count=" << count;
-
-    vector<AttributeData> values;
-    for (int i = 0; i < count; i++) {
-        auto data = bytes.view(length);
-        D << "data, size=" << data.getSize() << ", bytes=" << data.toString();
-        bytes.skip(length);
-        values.push_back(AttributeData::fromByteBuffer(data));
-    }
-
-    return values;
-}
-
-vector<AttributeData> AttPdu::parseReadByGroupType(ByteBuffer &bytes) {
-    return parse(bytes, READ_BY_GROUP_TYPE_RES);
-}
-
-vector<AttributeData> AttPdu::parseReadByType(ByteBuffer &bytes) {
-    return parse(bytes, READ_BY_TYPE_RES);
-}
-
-// -----------------------------------------------------------------------
-// AttributeData
-// -----------------------------------------------------------------------
-
-AttributeData AttributeData::fromByteBuffer(ByteBuffer &bytes) {
-    uint16_t handle = bytes.read16le();
-
-    return AttributeData(handle, bytes.view());
-}
-
-AttributeData::AttributeData(uint16_t handle, ByteBuffer value) :
-        handle(handle), value(value) {
-}
-
-AttributeData::~AttributeData() {
-}
-
-// -----------------------------------------------------------------------
-// Device
-// -----------------------------------------------------------------------
-
-BluetoothDevice::BluetoothDevice() {
-}
-
-BluetoothDevice::~BluetoothDevice() {
-}
-
-// -----------------------------------------------------------------------
-// Adapter
-// -----------------------------------------------------------------------
-
-BluetoothAdapter::BluetoothAdapter() {
-}
-
-BluetoothAdapter::~BluetoothAdapter() {
-}
-
-/*
-map<int, LinuxBluetoothAdapter *> adapters;
-
-BluetoothAdapter &getAdapter(int hciDevice) {
-    map<int, LinuxBluetoothAdapter *>::iterator it = adapters.find(hciDevice);
-
-    if (it == adapters.end()) {
-        LinuxBluetoothAdapter *adapter = new LinuxBluetoothAdapter(hciDevice);
-        adapters[hciDevice] = adapter;
-        return *adapter;
-    }
-
-    return *it->second;
-}
-*/
-
-BluetoothAdapter &getAdapter(int hciDevice) {
-    return getAdapterImpl(hciDevice);
-}
-
-void shutdown() {
-    shutdownImpl();
-}
-
-}
-};
diff --git a/Bluetooth.h b/Bluetooth.h
deleted file mode 100644
index 4314d8e..0000000
--- a/Bluetooth.h
+++ /dev/null
@@ -1,203 +0,0 @@
-#ifndef BLUETOOTH_H
-#define BLUETOOTH_H
-
-#include <string>
-#include <stdexcept>
-#include <boost/uuid/uuid.hpp>
-
-#include "ByteBuffer.h"
-
-namespace trygvis {
-namespace bluetooth {
-
-using namespace std;
-
-struct SpecUuid {
-public:
-    SpecUuid(uint16_t value) : value(value) {}
-    uint16_t value;
-};
-
-namespace uuids {
-static const SpecUuid PRIMARY_SERVICE = SpecUuid(0x2800);
-static const SpecUuid SECONDARY_SERVICE = SpecUuid(0x2801);
-static const SpecUuid CHARACTERISTIC = SpecUuid(0x2803);
-}
-
-class BluetoothAdapter;
-
-class BluetoothDevice;
-
-class BluetoothGattService;
-
-class BluetoothGattCharacteristic;
-
-class BluetoothException : public runtime_error {
-public:
-    BluetoothException(const BluetoothAdapter *adapter, string const &what) :
-            runtime_error(what), adapter(adapter), device(nullptr) {
-    }
-
-    BluetoothException(const BluetoothDevice *device, string const &what) :
-            runtime_error(what), adapter(nullptr), device(device) {
-    }
-
-    BluetoothException(string const &what) :
-            runtime_error(what), adapter(nullptr), device(nullptr) {
-    }
-
-    const BluetoothAdapter *adapter;
-    const BluetoothDevice *device;
-};
-
-class Mac {
-public:
-    Mac(uint8_t _0, uint8_t _1, uint8_t _2, uint8_t _3, uint8_t _4, uint8_t _5) {
-        bytes[0] = _0;
-        bytes[1] = _1;
-        bytes[2] = _2;
-        bytes[3] = _3;
-        bytes[4] = _4;
-        bytes[5] = _5;
-    };
-
-    string str() const;
-
-    bool operator==(Mac &other) const;
-
-    bool operator!=(Mac &other) const;
-
-    void copy(uint8_t &_0, uint8_t &_1, uint8_t &_2, uint8_t &_3, uint8_t &_4, uint8_t &_5) const;
-
-    static Mac parseMac(string s) throw(BluetoothException);
-
-    friend bool operator<(const Mac &a, const Mac &b);
-
-private:
-    uint8_t bytes[6];
-};
-
-class BluetoothGattCharacteristic {
-public:
-    virtual ~BluetoothGattCharacteristic() {
-    };
-
-    virtual BluetoothGattService &getService() const = 0;
-
-    virtual uint16_t getHandle() const = 0;
-
-    virtual const boost::uuids::uuid getUuid() const = 0;
-
-    virtual uint8_t getProperties() const = 0;
-
-    virtual uint16_t getValueHandle() const = 0;
-};
-
-class BluetoothGattService {
-public:
-    virtual ~BluetoothGattService() {
-    };
-
-    virtual BluetoothDevice &getDevice() const = 0;
-
-    virtual boost::uuids::uuid getUuid() const = 0;
-
-    virtual uint16_t getHandle() const = 0;
-
-    virtual uint16_t getEndGroupHandle() const = 0;
-
-    virtual const vector<BluetoothGattCharacteristic *> getCharacteristics() const = 0;
-
-    virtual void addCharacteristic(BluetoothGattCharacteristic* characteristic) = 0;
-};
-
-class BluetoothDevice {
-public:
-    BluetoothDevice();
-
-    virtual ~BluetoothDevice();
-
-    virtual Mac const &mac() = 0;
-
-    virtual BluetoothAdapter &adapter() = 0;
-
-    virtual void connect() = 0;
-
-    virtual void disconnect() = 0;
-
-    virtual void discoverServices() = 0;
-
-    virtual const vector<BluetoothGattService *> getServices() const = 0;
-};
-
-class BluetoothAdapter {
-public:
-    virtual void startScan() = 0;
-
-    virtual void stopScan() = 0;
-
-    virtual void runScan(void (callback)(BluetoothDevice &device)) = 0;
-
-    virtual BluetoothDevice &getDevice(Mac &mac) = 0;
-
-protected:
-    BluetoothAdapter();
-
-    virtual ~BluetoothAdapter();
-};
-
-enum AttPduType {
-    ERROR = 0x00,
-    INVALID_HANDLE = 0x01,
-    READ_BY_TYPE_REQ = 0x08,
-    READ_BY_TYPE_RES = 0x09,
-    READ_BY_GROUP_TYPE_REQ = 0x10,
-    READ_BY_GROUP_TYPE_RES = 0x11,
-};
-
-class AttributeData;
-
-class AttPdu {
-public:
-    AttPdu(ByteBuffer &bytes);
-
-    AttPdu(ByteBuffer &bytes, AttPduType type);
-
-    AttPduType getType();
-
-    static vector<AttributeData> parseReadByGroupType(ByteBuffer &bytes);
-
-    static vector<AttributeData> parseReadByType(ByteBuffer &bytes);
-
-    static void makeReadByGroupType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid);
-
-    static void makeReadByType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid);
-
-private:
-    static void checkType(ByteBuffer &bytes, AttPduType type);
-    static vector<AttributeData> parse(ByteBuffer &bytes, AttPduType type);
-
-    ByteBuffer &bytes;
-};
-
-class AttributeData {
-public:
-    ~AttributeData();
-
-    static AttributeData fromByteBuffer(ByteBuffer &value);
-
-    const uint16_t handle;
-    ByteBuffer value;
-
-private:
-    AttributeData(uint16_t handle, ByteBuffer value);
-};
-
-BluetoothAdapter &getAdapter(int hciDevice);
-
-void shutdown();
-
-}
-}
-
-#endif
diff --git a/BluetoothImpl.h b/BluetoothImpl.h
deleted file mode 100644
index 3fad164..0000000
--- a/BluetoothImpl.h
+++ /dev/null
@@ -1,147 +0,0 @@
-#ifndef BLUETOOTH_IMPL_H
-#define BLUETOOTH_IMPL_H
-
-#include "Bluetooth.h"
-#include <boost/uuid/uuid_io.hpp>
-
-#define BLUETOOTH_UUID_INITIALIZER \
-    { \
-        0x00, 0x00, 0x00, 0x00, \
-        0x00, 0x00, \
-        0x10, 0x00, \
-        0x80, 0x00, \
-        0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb \
-    };
-
-namespace trygvis {
-namespace bluetooth {
-
-typedef boost::uuids::uuid uuid_t;
-
-class DefaultBluetoothGattCharacteristic : public BluetoothGattCharacteristic {
-public:
-    DefaultBluetoothGattCharacteristic(BluetoothGattService &service, uint16_t handle, uuid_t uuid, uint8_t properties, uint16_t valueHandle)
-            : service(service), handle(handle), uuid(uuid), properties(properties), valueHandle(valueHandle) {
-    }
-
-    virtual ~DefaultBluetoothGattCharacteristic() {
-    }
-
-    BluetoothGattService &getService() const {
-        return service;
-    }
-
-    uint16_t getHandle() const {
-        return handle;
-    }
-
-    const uuid_t getUuid() const {
-        return uuid;
-    }
-
-    uint8_t getProperties() const {
-        return properties;
-    }
-
-    uint16_t getValueHandle() const {
-        return valueHandle;
-    }
-
-protected:
-    BluetoothGattService &service;
-    uint16_t handle;
-    uuid_t uuid;
-    uint8_t properties;
-    uint16_t valueHandle;
-};
-
-class DefaultBluetoothGattService : public BluetoothGattService {
-public:
-    DefaultBluetoothGattService(BluetoothDevice &device, const uuid_t uuid, const uint16_t handle, const uint16_t endGroupHandle)
-            : device(device), uuid(uuid), handle(handle), endGroupHandle(endGroupHandle) {
-        DF;
-    }
-
-    virtual ~DefaultBluetoothGattService() {
-        DF;
-        removeCharacteristics();
-    }
-
-    virtual BluetoothDevice &getDevice() const {
-        return device;
-    }
-
-    virtual uuid_t getUuid() const {
-        return uuid;
-    }
-
-    virtual uint16_t getHandle() const {
-        return handle;
-    }
-
-    virtual uint16_t getEndGroupHandle() const {
-        return endGroupHandle;
-    }
-
-    virtual const vector<BluetoothGattCharacteristic *> getCharacteristics() const {
-        return characteristics;
-    }
-
-    virtual void addCharacteristic(BluetoothGattCharacteristic *characteristic) {
-        characteristics.push_back(characteristic);
-    }
-
-protected:
-    BluetoothDevice &device;
-    const uuid_t uuid;
-    const uint16_t handle;
-    const uint16_t endGroupHandle;
-    vector<BluetoothGattCharacteristic *> characteristics;
-
-    void removeCharacteristics() {
-        DF;
-        for (auto &c: characteristics) {
-            delete c;
-        }
-        characteristics.clear();
-    }
-};
-
-class DefaultBluetoothDevice : public BluetoothDevice {
-public:
-    virtual const vector<BluetoothGattService *> getServices() const {
-        return services;
-    };
-
-    virtual void addService(BluetoothGattService *service) {
-        services.push_back(service);
-    }
-
-protected:
-    DefaultBluetoothDevice() {
-        DF;
-    }
-
-    virtual ~DefaultBluetoothDevice() {
-        DF;
-        removeServices();
-    }
-
-    void removeServices() {
-        for (auto s: services) {
-            delete s;
-        }
-        services.clear();
-    }
-
-    vector<BluetoothGattService *> services;
-};
-
-BluetoothAdapter &getAdapterImpl(int hciDevice);
-
-void shutdownImpl();
-
-}
-};
-
-#endif
diff --git a/ByteBuffer.cpp b/ByteBuffer.cpp
deleted file mode 100644
index 820c638..0000000
--- a/ByteBuffer.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-#include "ByteBuffer.h"
-#include <string.h>
-#include <sstream>
-#include <iomanip>
-
-using namespace std;
-
-ByteBuffer::ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity) :
-    bytes(bytes), capacity(capacity), zero(bytes.get()), end(&bytes.get()[capacity]) {
-    ptr = (uint8_t *) zero;
-}
-
-ByteBuffer::ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, size_t zero, size_t size) :
-        bytes(bytes), capacity(capacity), zero(&bytes.get()[zero]), end(&bytes.get()[size]) {
-    assert(zero <= size);
-    assert(size <= capacity);
-    ptr = (uint8_t *) zero;
-}
-
-ByteBuffer::ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, const uint8_t *zero, const uint8_t *end) :
-        bytes(bytes), capacity(capacity), zero(zero), end(end), ptr((uint8_t *) zero) {
-}
-
-ByteBuffer &ByteBuffer::write8(uint8_t value) {
-    checkAndUpdateEnd(1);
-    (*ptr++) = value;
-    return *this;
-}
-
-ByteBuffer &ByteBuffer::write16le(uint16_t value) {
-    checkAndUpdateEnd(2);
-    (*ptr++) = (uint8_t) (value & 0xff);
-    (*ptr++) = (uint8_t) ((value >> 8) & 0xff);
-    return *this;
-}
-
-uint8_t ByteBuffer::get8(size_t index) const {
-    assertCanAccessRelative(index);
-    return ptr[index];
-}
-
-uint8_t ByteBuffer::read8() {
-    assertCanAccessRelative(0);
-    return *ptr++;
-}
-
-uint16_t ByteBuffer::read16le() {
-    assertCanAccessRelative(0);
-    uint16_t value;
-    value = *ptr++;
-    value |= ((uint16_t) *ptr++) << 8;
-    return value;
-}
-
-void ByteBuffer::copy(uint8_t *bytes, size_t length) const {
-    assertCanAccessRelative(length - 1);
-
-    memcpy(bytes, ptr, length);
-}
-
-ByteBuffer ByteBuffer::view() const {
-//    DF << "cursor=" << getCursor() << ", size=" << getSize() << ", new size=" << end - ptr << ", ptr=" << (uint64_t) ptr << ", zero=" << (uint64_t) zero;
-    return view(ptr, end);
-}
-
-ByteBuffer ByteBuffer::view(size_t length) const {
-    return ByteBuffer(bytes, length, ptr, ptr + length);
-}
-
-ByteBuffer ByteBuffer::view(uint8_t *ptr, const uint8_t *end) const {
-    return ByteBuffer(bytes, end - ptr, ptr, end);
-}
-
-void ByteBuffer::checkAndUpdateEnd(size_t newBytes) {
-    uint8_t *newEnd = ptr + newBytes;
-    if (newEnd >= end) {
-        if (newEnd >= &zero[capacity]) {
-            throw ByteBufferException(string("New size is too large! cursor=") + to_string(getCursor()) + ", size=" + to_string(getSize()) + ", capacity=" + to_string(capacity) + ", new bytes=" + to_string(newBytes));
-        }
-        end = newEnd;
-    }
-}
-
-void ByteBuffer::assertCanAccessRelative(size_t diff) const {
-    assertCanAccessIndex(ptr + diff);
-}
-
-void ByteBuffer::assertCanAccessIndex(uint8_t *p) const {
-    if (p >= end || p < zero) {
-        throw ByteBufferException(string("Out of bounds! size=") + to_string(getSize()) + ", index=" + to_string(p - zero));
-    }
-}
-
-std::string ByteBuffer::toString() const {
-    stringstream s;
-
-    for (uint8_t *i = (uint8_t *) zero; i < end; i++) {
-        s << hex << setfill('0') << setw(2) << (int) *i << " ";
-    }
-
-    return string(s.str());
-}
diff --git a/ByteBuffer.h b/ByteBuffer.h
deleted file mode 100644
index 3836966..0000000
--- a/ByteBuffer.h
+++ /dev/null
@@ -1,91 +0,0 @@
-#ifndef BYTE_STREAM_WRAPPER_H
-#define BYTE_STREAM_WRAPPER_H
-
-#include <cstdint>
-#include <cstdlib>
-#include <string>
-#include <stdexcept>
-
-// For now
-#include "log.h"
-
-class ByteBufferException : public std::runtime_error {
-public:
-    ByteBufferException(std::string const &what) : std::runtime_error(what) {
-    }
-};
-
-class ByteBuffer {
-public:
-    ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity);
-
-    ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, size_t zero, size_t size);
-
-    inline size_t getSize() const {
-//        DF << "end=" << (uint64_t)end << ", zero=" << (uint64_t)zero << ", size=" << (end - zero);
-        return end - zero;
-    }
-
-    inline size_t getCapacity() const {
-        return capacity;
-    }
-
-    inline size_t getCursor() const {
-        return ptr - zero;
-    }
-
-    inline size_t getBytesLeft() const {
-        return end - ptr;
-    }
-
-    inline void setCursor(size_t newCursor) {
-//        assertCanAccessRelative(newCursor);
-        ptr = (uint8_t *) &zero[newCursor];
-    }
-
-    inline void skip(size_t length) {
-//        checkAndUpdateEnd(length);
-        ptr += length;
-    }
-
-    ByteBuffer &write8(uint8_t value);
-
-    ByteBuffer &write16le(uint16_t value);
-
-    uint8_t get8(size_t index) const;
-
-    uint8_t read8();
-
-    uint16_t read16le();
-
-    void copy(uint8_t *bytes, size_t length) const;
-
-    /**
-    * Creates a view from cursor to size.
-    */
-    ByteBuffer view() const;
-
-    // TODO: should return const
-    ByteBuffer view(size_t length) const;
-
-    std::string toString() const;
-
-private:
-    ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, const uint8_t *zero, const uint8_t *end);
-
-    ByteBuffer view(uint8_t *ptr, const uint8_t *end) const;
-
-    void checkAndUpdateEnd(size_t count);
-
-    void assertCanAccessRelative(size_t diff) const;
-
-    void assertCanAccessIndex(uint8_t *p) const;
-
-    const std::shared_ptr<uint8_t> bytes;
-    const size_t capacity;
-    const uint8_t *zero;
-    const uint8_t *end;
-    uint8_t *ptr;
-};
-
-#endif
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bd0d7a7..a6e2d6e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,65 +3,15 @@ project(ble_toys C CXX)
 find_package(PkgConfig)
 
 # Use Clang by default: http://stackoverflow.com/a/7031553/245614
-
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
 
-set(SOURCE_FILES "${SOURCE_FILES}"
-    Bluetooth.cpp
-    LinuxBluetooth.cpp
-    ByteBuffer.cpp)
-
-add_library(ble ${SOURCE_FILES})
-
-add_executable(ble_toys main.cpp)
-target_link_libraries(ble_toys ble)
-
 # Boost
 set(Boost_USE_STATIC_LIBS OFF)
 set(Boost_USE_MULTITHREADED OFF)
 set(Boost_USE_STATIC_RUNTIME OFF)
 
-find_package(Boost COMPONENTS system log thread REQUIRED)
-target_link_libraries(ble_toys ${Boost_LIBRARIES})
-
 add_definitions(-DBOOST_ALL_DYN_LINK)
 
-# Bluez
-pkg_check_modules(BLUEZ bluez REQUIRED)
-target_link_libraries(ble_toys ${BLUEZ_LIBRARIES})
-
-# pthreads
-find_package(Threads REQUIRED)
-target_link_libraries(ble_toys ${CMAKE_THREAD_LIBS_INIT})
-
-enable_testing()
-find_package(Boost COMPONENTS log unit_test_framework REQUIRED)
-
-# If we can change directory here add_definition and test-specific stuff could be moved to the test directory
-file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} test/*.cpp)
-add_definitions(-DBOOST_TEST_DYN_LINK)
-
-foreach(testSrc ${TEST_SRCS})
-    #Extract the filename without an extension (NAME_WE)
-    get_filename_component(testName ${testSrc} NAME_WE)
-
-    #Add compile target
-    add_executable(${testName} ${testSrc})
-
-    include_directories(${PROJECT_SOURCE_DIR})
-
-    #link to Boost libraries AND your targets and dependencies
-    target_link_libraries(${testName} ble)
-    target_link_libraries(${testName} pthread)
-    target_link_libraries(${testName} ${Boost_LIBRARIES})
-
-    #I like to move testing binaries into a testBin directory
-    set_target_properties(${testName} PROPERTIES
-        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test)
-
-    #Finally add it to test execution -
-    #Notice the WORKING_DIRECTORY and COMMAND
-    add_test(NAME ${testName}
-             WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test
-             COMMAND ${CMAKE_BINARY_DIR}/test/${testName} )
-endforeach(testSrc)
+add_subdirectory(ble)
+add_subdirectory(apps)
+add_subdirectory(test)
diff --git a/LinuxBluetooth.cpp b/LinuxBluetooth.cpp
deleted file mode 100644
index 4b7f0e0..0000000
--- a/LinuxBluetooth.cpp
+++ /dev/null
@@ -1,512 +0,0 @@
-#include "BluetoothImpl.h"
-
-#include <string.h>
-#include <bluetooth/bluetooth.h>
-#include <bluetooth/hci.h>
-#include <bluetooth/hci_lib.h>
-#include <bluetooth/l2cap.h>
-#include <map>
-#include <sstream>
-#include <iomanip>
-#include <numeric>
-
-// Got to love magic constants. Taken from bluez.git/tools/btgatt-client.c
-#define ATT_CID 4
-
-#define MAX_MTU 256
-
-namespace trygvis {
-namespace bluetooth {
-namespace linux {
-
-typedef boost::uuids::uuid uuid_t;
-
-using namespace uuids;
-
-class LinuxBluetoothDevice;
-
-class LinuxBluetoothAdapter;
-
-class LinuxBluetoothManager;
-
-class LinuxBluetoothAdapter : public BluetoothAdapter {
-public:
-    LinuxBluetoothAdapter(int hciDeviceId);
-
-    ~LinuxBluetoothAdapter();
-
-    void runScan(void (*callback)(BluetoothDevice &device)) override;
-
-    BluetoothDevice &getDevice(Mac &mac) override;
-
-private:
-    void startScan() override;
-
-    void stopScan() override;
-
-    int hciDeviceId;
-    int hciSocket;
-    struct hci_filter hciFilter;
-    bool scanning;
-
-    map<Mac, LinuxBluetoothDevice *> devices;
-};
-
-class LinuxBluetoothDevice : public DefaultBluetoothDevice {
-public:
-    LinuxBluetoothDevice(LinuxBluetoothAdapter &adapter, Mac mac);
-
-    Mac const &mac() override;
-
-    LinuxBluetoothAdapter &adapter() override;
-
-    void connect() override;
-
-    void disconnect() override;
-
-    void discoverServices() override;
-
-private:
-    vector<AttributeData> discoverServices(uint16_t startHandle);
-
-    vector<AttributeData> discoverCharacteristics(uint16_t startHandle, uint16_t endHandle);
-
-    ByteBuffer writeAndRead(ByteBuffer &out, shared_ptr<uint8_t> buffer, size_t size);
-
-    uuid_t readUuid(const ByteBuffer &bytes) const;
-
-    LinuxBluetoothAdapter &_adapter;
-    Mac _mac;
-    int l2cap;
-};
-
-// Utilities
-
-string errnoAsString() {
-    return string(strerror(errno));
-};
-
-// -----------------------------------------------------------------------
-// Mac
-// -----------------------------------------------------------------------
-
-Mac parseMac(bdaddr_t &a) {
-    return Mac(a.b[0], a.b[1], a.b[2], a.b[3], a.b[4], a.b[5]);
-}
-
-// -----------------------------------------------------------------------
-// Device
-// -----------------------------------------------------------------------
-
-LinuxBluetoothDevice::LinuxBluetoothDevice(LinuxBluetoothAdapter &adapter, Mac mac) :
-        DefaultBluetoothDevice(), _adapter(adapter), _mac(mac) {
-}
-
-Mac const &LinuxBluetoothDevice::mac() {
-    return _mac;
-}
-
-LinuxBluetoothAdapter &LinuxBluetoothDevice::adapter() {
-    return _adapter;
-}
-
-void LinuxBluetoothDevice::connect() {
-    struct sockaddr_l2 addr;
-
-    D << "connect: mac=" << _mac.str();
-
-    l2cap = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
-    if (l2cap < 0) {
-        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): socket(): " + errnoAsString());
-    }
-
-    memset(&addr, 0, sizeof(addr));
-    addr.l2_family = AF_BLUETOOTH;
-    addr.l2_bdaddr = {{0, 0, 0, 0, 0, 0}};
-    addr.l2_cid = htobs(ATT_CID);
-    addr.l2_bdaddr_type = BDADDR_LE_PUBLIC;
-
-    if (bind(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
-        close(l2cap);
-        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): bind(): " + errnoAsString());
-    }
-
-    struct bt_security btsec;
-    memset(&btsec, 0, sizeof(btsec));
-    btsec.level = BT_SECURITY_LOW;
-    if (setsockopt(l2cap, SOL_BLUETOOTH, BT_SECURITY, &btsec, sizeof(btsec)) != 0) {
-        close(l2cap);
-        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): setsockopt(): " + errnoAsString());
-    }
-
-    memset(&addr, 0, sizeof(addr));
-    addr.l2_family = AF_BLUETOOTH;
-    addr.l2_cid = htobs(ATT_CID);
-    addr.l2_bdaddr_type = BDADDR_LE_RANDOM;
-    _mac.copy(addr.l2_bdaddr.b[5],
-            addr.l2_bdaddr.b[4],
-            addr.l2_bdaddr.b[3],
-            addr.l2_bdaddr.b[2],
-            addr.l2_bdaddr.b[1],
-            addr.l2_bdaddr.b[0]);
-
-    if (::connect(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
-        close(l2cap);
-        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): connect(): " + errnoAsString());
-    }
-}
-
-void LinuxBluetoothDevice::disconnect() {
-    DF << "mac = " << _mac.str();
-    close(l2cap);
-}
-
-uuid_t LinuxBluetoothDevice::readUuid(const ByteBuffer &bytes) const {
-    size_t bytesLeft = bytes.getBytesLeft();
-
-    uuid_t u;
-
-    if (bytesLeft == 2) {
-        uint8_t bs[16] = BLUETOOTH_UUID_INITIALIZER;
-        bs[2] = bytes.get8(1);
-        bs[3] = bytes.get8(0);
-        memcpy(&u, bs, 16);
-    } else if (bytesLeft == 16) {
-        uint8_t bs[16];
-        bs[15] = bytes.get8(0);
-        bs[14] = bytes.get8(1);
-        bs[13] = bytes.get8(2);
-        bs[12] = bytes.get8(3);
-        bs[11] = bytes.get8(4);
-        bs[10] = bytes.get8(5);
-        bs[9] = bytes.get8(6);
-        bs[8] = bytes.get8(7);
-        bs[7] = bytes.get8(8);
-        bs[6] = bytes.get8(9);
-        bs[5] = bytes.get8(10);
-        bs[4] = bytes.get8(11);
-        bs[3] = bytes.get8(12);
-        bs[2] = bytes.get8(13);
-        bs[1] = bytes.get8(14);
-        bs[0] = bytes.get8(15);
-        memcpy(&u, bs, 16);
-    } else {
-        throw BluetoothException(this, "Unexpected bytes left: " + to_string(bytesLeft));
-    }
-
-    return u;
-}
-
-void LinuxBluetoothDevice::discoverServices() {
-    uint16_t startHandle = 0x0001;
-
-    removeServices();
-
-    do {
-        vector<AttributeData> values = discoverServices(startHandle);
-
-        // Shouldn't happen, but you never know.
-        if (values.size() == 0) {
-            break;
-        }
-
-        uint16_t endGroupHandle;
-
-        for (auto &data: values) {
-            endGroupHandle = data.value.read16le();
-
-//            D << "handle: 0x" << hex << setw(4) << setfill('0') << data.handle <<
-//                        ", endGroupHandle: 0x" << hex << setw(4) << setfill('0') << endGroupHandle <<
-//                        ", value: " << data.value.toString();
-
-            uuid_t u = readUuid(data.value);
-
-            addService(new DefaultBluetoothGattService(*this, u, data.handle, endGroupHandle));
-        }
-
-        auto last = values.back();
-
-        startHandle = endGroupHandle;
-    } while (startHandle != 0xffff);
-
-//    for (auto &s : services) {
-//        D << "service: " << to_string(s->getUuid()) << ", handle: " << s->getHandle() << ", end group handle: " << s->getEndGroupHandle();
-//        D << "s= " << (uint64_t) s;
-//    }
-
-    auto it = services.begin(),
-            end = services.end();
-
-    if (it == end) {
-        return;
-    }
-
-    startHandle = 0x0001;
-    vector<AttributeData> chars;
-
-    auto s = *it;
-
-    do {
-        vector<AttributeData> values = discoverCharacteristics(startHandle, 0xffff);
-
-        if (values.size() == 0) {
-            break;
-        }
-
-        uint16_t lastHandle = 0xffff;
-
-        for (auto &c : values) {
-            while (c.handle > s->getEndGroupHandle() && it != end) {
-                s = *it++;
-//                D << "service: " << s->getHandle();
-            }
-
-            lastHandle = c.handle;
-
-            uint8_t properties = c.value.read8();
-            uint16_t valueHandle = c.value.read16le();
-            uuid_t uuid = readUuid(c.value);
-
-//            D << "characteristic: handle: " << setw(2) << setfill('0') << hex << (int) c.handle <<
-//                        ", properties: " << setw(2) << setfill('0') << hex << (int) properties <<
-//                        ", valueHandle: 0x" << setw(4) << setfill('0') << hex << (int) valueHandle <<
-//                        ", uuid: " << uuid;
-
-            s->addCharacteristic(new DefaultBluetoothGattCharacteristic(*s, c.handle, uuid, properties, valueHandle));
-        }
-
-        auto last = values.back();
-
-        startHandle = lastHandle + (uint8_t) 2;
-    } while (startHandle != 0xffff);
-}
-
-ByteBuffer LinuxBluetoothDevice::writeAndRead(ByteBuffer &out, shared_ptr<uint8_t> buffer, size_t size) {
-    D << "pdu size=" << out.getCursor();
-    ssize_t written = write(l2cap, buffer.get(), out.getCursor());
-
-    D << "written=" << written;
-
-    ssize_t r = read(l2cap, buffer.get(), size);
-
-    if (r == -1) {
-        throw BluetoothException(this, "read(): " + errnoAsString());
-    }
-
-    auto in = ByteBuffer(buffer, (size_t) r);
-
-    D << "read: " << r << " bytes: " << in.toString();
-
-    return in;
-}
-
-vector<AttributeData> LinuxBluetoothDevice::discoverServices(uint16_t startHandle) {
-    DF;
-
-    shared_ptr<uint8_t> buffer(new uint8_t[MAX_MTU]);
-    ByteBuffer out = ByteBuffer(buffer, MAX_MTU);
-
-    AttPdu::makeReadByGroupType(out, startHandle, 0xffff, uuids::PRIMARY_SERVICE);
-
-    ByteBuffer in = writeAndRead(out, buffer, MAX_MTU);
-
-    vector<AttributeData> values = AttPdu::parseReadByGroupType(in);
-
-    D << "READ_BY_GROUP_TYPE response has " + to_string(values.size()) + " values";
-
-    return values;
-}
-
-vector<AttributeData> LinuxBluetoothDevice::discoverCharacteristics(uint16_t startHandle, uint16_t endHandle) {
-    DF;
-
-    shared_ptr<uint8_t> buffer(new uint8_t[MAX_MTU]);
-    ByteBuffer out = ByteBuffer(buffer, MAX_MTU);
-
-    AttPdu::makeReadByType(out, startHandle, endHandle, uuids::CHARACTERISTIC);
-
-    ByteBuffer in = writeAndRead(out, buffer, MAX_MTU);
-
-    vector<AttributeData> values = AttPdu::parseReadByType(in);
-
-    D << "READ_BY_TYPE response has " + to_string(values.size()) + " values";
-
-    return values;
-}
-
-// -----------------------------------------------------------------------
-// Adapter
-// -----------------------------------------------------------------------
-
-LinuxBluetoothAdapter::LinuxBluetoothAdapter(int hciDeviceId) :
-        scanning(false) {
-    DF << "hciDeviceId=" << hciDeviceId;
-
-    this->hciDeviceId = hciDeviceId;
-    hciSocket = ::hci_open_dev(hciDeviceId);
-
-    D << "HCI socket: " << hciSocket;
-
-    if (hciSocket == -1) {
-        throw BluetoothException(this, "Could not open HCI device " + to_string(hciDeviceId));
-    }
-
-    hci_filter_clear(&hciFilter);
-    hci_filter_set_ptype(HCI_EVENT_PKT, &hciFilter);
-    hci_filter_set_event(EVT_LE_META_EVENT, &hciFilter);
-    hci_filter_set_event(EVT_LE_ADVERTISING_REPORT, &hciFilter);
-    setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &hciFilter, sizeof(hciFilter));
-}
-
-LinuxBluetoothAdapter::~LinuxBluetoothAdapter() {
-    DF;
-
-    stopScan();
-
-    close(hciSocket);
-
-    for (auto &pair : devices) {
-        delete pair.second;
-    }
-}
-
-void LinuxBluetoothAdapter::startScan() {
-    DF;
-
-    struct hci_dev_info di;
-
-    if (hci_devinfo(hciDeviceId, &di) < 0) {
-        throw BluetoothException(this, "HCI adapter is not up: " + to_string(hciDeviceId));
-    }
-
-    D << "hciDeviceId.dev_id=" << di.dev_id;
-    D << "hciDeviceId.bdaddr=" << parseMac(di.bdaddr).str();
-    D << "hciDeviceId.flags=" << setw(8) << setfill('0') << hex << di.flags;
-    D << "hciDeviceId.flags RUNNING = " << hci_test_bit(HCI_RUNNING, &di.flags);
-    D << "hciDeviceId.flags UP      = " << hci_test_bit(HCI_UP, &di.flags);
-    D << "hciDeviceId.flags PSCAN   = " << hci_test_bit(HCI_PSCAN, &di.flags);
-    D << "hciDeviceId.flags ISCAN   = " << hci_test_bit(HCI_ISCAN, &di.flags);
-    D << "hciDeviceId.name=" << di.name;
-
-    int up = hci_test_bit(HCI_UP, &di.flags);
-
-    if (!up) {
-        throw BluetoothException(this, "HCI adapter is not up: " + to_string(hciDeviceId));
-    }
-
-    if (hci_le_set_scan_parameters(hciSocket, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0, 1000) < 0) {
-        throw BluetoothException(this, "hci_le_set_scan_parameters: " + errnoAsString());
-    }
-
-    if (hci_le_set_scan_enable(hciSocket, 1, 0, 1000)) {
-        throw BluetoothException(this, "Could not start scanning: other" + errnoAsString());
-    }
-
-    scanning = true;
-}
-
-void LinuxBluetoothAdapter::stopScan() {
-    DF;
-
-    if (!scanning) {
-        return;
-    }
-
-    scanning = false;
-
-    if (hci_le_set_scan_enable(hciSocket, 0, 0, 1000) < 0) {
-        W << "stopScan: hci_le_set_scan_enable: " << errnoAsString();
-    }
-}
-
-BluetoothDevice &LinuxBluetoothAdapter::getDevice(Mac &mac) {
-    map<Mac, LinuxBluetoothDevice *>::iterator it = devices.find(mac);
-
-    if (it == devices.end()) {
-        LinuxBluetoothDevice *device = new LinuxBluetoothDevice(*this, mac);
-        devices[mac] = device;
-        return *device;
-    }
-
-    return *it->second;
-}
-
-void LinuxBluetoothAdapter::runScan(void (*callback)(BluetoothDevice &device)) {
-    fd_set rfds;
-    FD_ZERO(&rfds);
-    FD_SET(hciSocket, &rfds);
-
-    startScan();
-
-    while (scanning) {
-        // Linux can change tv, so it has to be reinitialized
-        struct timeval tv;
-        tv.tv_sec = 1;
-        tv.tv_usec = 0;
-
-        int selected = select(hciSocket + 1, &rfds, NULL, NULL, &tv);
-
-        if (selected == -1) {
-            throw BluetoothException(this, "select() failed");
-        }
-
-        if (selected == 0) {
-            D << "timeout";
-            // Timeout, just continue
-            continue;
-        }
-
-        unsigned char hciEventBuf[HCI_MAX_EVENT_SIZE];
-
-        ssize_t len = read(hciSocket, hciEventBuf, sizeof(hciEventBuf));
-        evt_le_meta_event *metaEvent = (evt_le_meta_event *) (hciEventBuf + (1 + HCI_EVENT_HDR_SIZE));
-        len -= (1 + HCI_EVENT_HDR_SIZE);
-
-        D << "metaEvent->subevent = " << std::hex << (int) metaEvent->subevent;
-
-        if (metaEvent->subevent == EVT_LE_ADVERTISING_REPORT) {
-            le_advertising_info *advertisingInfo = (le_advertising_info *) (metaEvent->data + 1);
-
-            Mac mac = parseMac(advertisingInfo->bdaddr);
-
-            BluetoothDevice &device = getDevice(mac);
-
-            callback(device);
-        }
-    }
-}
-
-}
-}
-}
-
-// -----------------------------------------------------------------------
-// Implementation of platform-specific method.
-// -----------------------------------------------------------------------
-
-namespace trygvis {
-namespace bluetooth {
-using namespace trygvis::bluetooth::linux;
-
-map<int, LinuxBluetoothAdapter *> adapters;
-
-BluetoothAdapter &getAdapterImpl(int hciDevice) {
-    map<int, LinuxBluetoothAdapter *>::iterator it = adapters.find(hciDevice);
-
-    if (it == adapters.end()) {
-        LinuxBluetoothAdapter *adapter = new LinuxBluetoothAdapter(hciDevice);
-        adapters[hciDevice] = adapter;
-        return *adapter;
-    }
-
-    return *it->second;
-}
-
-void shutdownImpl() {
-    for (auto &pair: adapters) {
-        delete pair.second;
-    }
-}
-
-}
-}
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
new file mode 100644
index 0000000..a138336
--- /dev/null
+++ b/apps/CMakeLists.txt
@@ -0,0 +1,22 @@
+set(APPS sm-get-value ble-inspect-device)
+
+# Boost
+find_package(Boost COMPONENTS system log thread REQUIRED)
+
+# Bluez
+pkg_check_modules(BLUEZ bluez REQUIRED)
+
+# pthreads
+find_package(Threads REQUIRED)
+
+foreach(app ${APPS})
+    include_directories("${PROJECT_SOURCE_DIR}/ble")
+
+    add_executable(${app} ${app}.cpp)
+    add_dependencies(${app} ble)
+
+    target_link_libraries(${app} ble)
+    target_link_libraries(${app} ${Boost_LIBRARIES})
+    target_link_libraries(${app} ${BLUEZ_LIBRARIES})
+    target_link_libraries(${app} ${CMAKE_THREAD_LIBS_INIT})
+endforeach(app)
diff --git a/apps/ble-inspect-device.cpp b/apps/ble-inspect-device.cpp
new file mode 100644
index 0000000..361311c
--- /dev/null
+++ b/apps/ble-inspect-device.cpp
@@ -0,0 +1,72 @@
+#include <exception>
+#include <iostream>
+#include <vector>
+#include <boost/uuid/uuid_io.hpp>
+#include "Bluetooth.h"
+
+using namespace std;
+using namespace trygvis::bluetooth;
+
+Mac *targetMac;
+
+void scan_callback(BluetoothDevice &device) {
+    device.adapter().stopScan();
+
+    if (device.mac() != *targetMac) {
+        cout << "found device: " << device.mac().str() << ", but not the one we want " << targetMac->str() << endl;
+        return;
+    }
+
+    cout << "Connecting to device: " << device.mac().str() << endl;
+
+    device.connect();
+
+    device.discoverServices();
+
+    vector<BluetoothGattService *> services = device.getServices();
+    cout << "Device has " << services.size() << " services" << endl;
+    
+    for (auto &s: services) {
+        const vector<BluetoothGattCharacteristic *> characteristics = s->getCharacteristics();
+
+        cout << "Service: UUID: " << s->getUuid() << ", has " << characteristics.size() << " characteristics" << endl;
+
+        for (auto &c: characteristics) {
+            cout << "Characteristic: UUID: " << c->getUuid() << ", properties: " << (int) c->getProperties() << endl;
+        }
+    }
+
+    device.disconnect();
+}
+
+int main(int argc, char *argv[]) {
+    if (argc != 2) {
+        cerr << "usage: " << argv[0] << " [mac]" << endl;
+        return EXIT_FAILURE;
+    }
+
+    int e;
+//    try {
+    Mac mac = Mac::parseMac(argv[1]);
+    targetMac = &mac;
+
+    BluetoothAdapter &adapter = getAdapter(0);
+
+    BluetoothDevice &device = adapter.getDevice(mac);
+
+    scan_callback(device);
+
+//        adapter->runScan(scan_callback);
+
+    e = EXIT_SUCCESS;
+//    } catch (std::runtime_error ex) {
+//        W << "std::runtime_error: " << ex.what();
+//        e = EXIT_FAILURE;
+//    } catch (std::exception ex) {
+//        W << "std::exception: " << ex.what();
+//        e = EXIT_FAILURE;
+//    }
+
+    shutdown();
+    return e;
+}
diff --git a/apps/sm-get-value.cpp b/apps/sm-get-value.cpp
new file mode 100644
index 0000000..232c166
--- /dev/null
+++ b/apps/sm-get-value.cpp
@@ -0,0 +1,75 @@
+#include <exception>
+#include <iostream>
+#include <vector>
+#include <boost/uuid/uuid_io.hpp>
+#include <boost/optional.hpp>
+#include "Bluetooth.h"
+#include "soil-moisture.h"
+
+using namespace std;
+using namespace trygvis::bluetooth;
+
+Mac *targetMac;
+
+void scan_callback(BluetoothDevice &device) {
+    device.adapter().stopScan();
+
+    if (device.mac() != *targetMac) {
+        cout << "found device: " << device.mac().str() << ", but not the one we want " << targetMac->str() << endl;
+        return;
+    }
+
+    cout << "Connecting to device: " << device.mac().str() << endl;
+
+    device.connect();
+
+    device.discoverServices();
+
+    vector<BluetoothGattService *> services = device.getServices();
+    cout << "Device has " << services.size() << " services" << endl;
+    
+    for (auto &s: services) {
+        const vector<BluetoothGattCharacteristic *> characteristics = s->getCharacteristics();
+
+        cout << "Service: UUID: " << s->getUuid() << ", has " << characteristics.size() << " characteristics" << endl;
+
+        for (auto &c: characteristics) {
+            cout << "Characteristic: UUID: " << c->getUuid() << ", properties: " << (int) c->getProperties() << endl;
+        }
+    }
+
+    boost::uuids::uuid soil_moisture_service;
+    boost::optional<BluetoothGattService*> service = device.findService(soil_moisture_service);
+
+    device.disconnect();
+}
+
+int main(int argc, char *argv[]) {
+    if (argc != 2) {
+        cerr << "usage: " << argv[0] << " [mac]" << endl;
+        return EXIT_FAILURE;
+    }
+
+    int e;
+//    try {
+    Mac mac = Mac::parseMac(argv[1]);
+    targetMac = &mac;
+
+    BluetoothAdapter &adapter = getAdapter(0);
+
+    BluetoothDevice &device = adapter.getDevice(mac);
+
+    scan_callback(device);
+
+    e = EXIT_SUCCESS;
+//    } catch (std::runtime_error ex) {
+//        W << "std::runtime_error: " << ex.what();
+//        e = EXIT_FAILURE;
+//    } catch (std::exception ex) {
+//        W << "std::exception: " << ex.what();
+//        e = EXIT_FAILURE;
+//    }
+
+    shutdown();
+    return e;
+}
diff --git a/apps/soil-moisture.h b/apps/soil-moisture.h
new file mode 100644
index 0000000..4f19de1
--- /dev/null
+++ b/apps/soil-moisture.h
@@ -0,0 +1,118 @@
+#ifndef SOIL_MOISTURE_H
+#define SOIL_MOISTURE_H
+
+#define SENSOR_NAME_LEN 10
+
+enum sm_cmd_code {
+    SM_CMD_GET_SENSOR_COUNT = 1,
+    SM_CMD_GET_VALUE = 2,
+    SM_CMD_SET_WARNING_VALUE = 3,
+    SM_CMD_GET_WARNING_VALUE = 4,
+    SM_CMD_SET_SENSOR_NAME = 5,
+    SM_CMD_GET_SENSOR_NAME = 6,
+    SM_CMD_SET_UPDATE_INTERVAL = 7,
+    SM_CMD_FAIL = 255,
+};
+
+struct sm_get_sensor_count_req {
+} __attribute__((packed));
+
+struct sm_get_sensor_count_res {
+    uint8_t count;
+} __attribute__((packed));
+
+struct sm_get_value_req {
+    uint8_t sensor;
+} __attribute__((packed));
+
+struct sm_get_value_res {
+    uint16_t value;
+} __attribute__((packed));
+
+struct sm_set_warning_value_req {
+    uint8_t sensor;
+    uint16_t warning_value;
+} __attribute__((packed));
+
+struct sm_set_warning_value_res {
+} __attribute__((packed));
+
+struct sm_get_warning_value_req {
+    uint8_t sensor;
+} __attribute__((packed));
+
+struct sm_get_warning_value_res {
+    uint16_t warning_value;
+} __attribute__((packed));
+
+struct sm_set_sensor_name_req {
+    uint8_t sensor;
+    uint8_t length;
+    uint8_t name[SENSOR_NAME_LEN];
+} __attribute__((packed));
+
+struct sm_set_sensor_name_res {
+} __attribute__((packed));
+
+struct sm_get_sensor_name_req {
+    uint8_t sensor;
+} __attribute__((packed));
+
+struct sm_get_sensor_name_res {
+    uint8_t length;
+    uint8_t name[SENSOR_NAME_LEN];
+} __attribute__((packed));
+
+struct sm_set_update_interval_req {
+    uint8_t sensor;
+    uint8_t interval_in_seconds;
+} __attribute__((packed));
+
+struct sm_set_update_interval_res {
+} __attribute__((packed));
+
+struct sm_req {
+    uint8_t code;
+    union {
+        struct sm_get_sensor_count_req get_sensor_count;
+        struct sm_get_value_req get_value;
+        struct sm_set_warning_value_req set_warning_value;
+        struct sm_get_warning_value_req get_warning_value;
+        struct sm_set_sensor_name_req set_sensor_name;
+        struct sm_get_sensor_name_req get_sensor_name;
+        struct sm_set_update_interval_req set_update_interval;
+    } __attribute__((packed));
+} __attribute__((packed));
+
+// len + code
+#define SM_RES_HEADER_SIZE 1
+
+struct sm_res {
+    // header
+    uint8_t code;
+
+    // body
+    union {
+        struct sm_get_sensor_count_res get_sensor_count;
+        struct sm_get_value_res get_value;
+        struct sm_set_warning_value_res set_warning_value;
+        struct sm_get_warning_value_res get_warning_value;
+        struct sm_set_sensor_name_res set_sensor_name;
+        struct sm_get_sensor_name_res get_sensor_name;
+        struct sm_set_update_interval_res set_update_interval;
+    } __attribute__((packed));
+} __attribute__((packed));
+
+#ifndef SM_DEBUG
+#define SM_DEBUG 1
+#endif
+
+#if SM_DEBUG == 1
+
+void write_req(struct sm_req const &req);
+
+void write_res(struct sm_res const &res);
+
+#endif
+
+#endif
diff --git a/ble/Bluetooth.cpp b/ble/Bluetooth.cpp
new file mode 100644
index 0000000..b135282
--- /dev/null
+++ b/ble/Bluetooth.cpp
@@ -0,0 +1,193 @@
+#include "Bluetooth.h"
+#include "BluetoothImpl.h"
+
+#include <sstream>
+#include <iomanip>
+#include <string.h>
+
+namespace trygvis {
+namespace bluetooth {
+using namespace std;
+
+// -----------------------------------------------------------------------
+// Mac
+// -----------------------------------------------------------------------
+
+string Mac::str() const {
+    std::ostringstream buf;
+
+    buf
+            << setw(2) << hex << setfill('0') << (int) bytes[0] << ":"
+            << setw(2) << hex << setfill('0') << (int) bytes[1] << ":"
+            << setw(2) << hex << setfill('0') << (int) bytes[2] << ":"
+            << setw(2) << hex << setfill('0') << (int) bytes[3] << ":"
+            << setw(2) << hex << setfill('0') << (int) bytes[4] << ":"
+            << setw(2) << hex << setfill('0') << (int) bytes[5];
+
+    return buf.str();
+}
+
+bool Mac::operator==(Mac &other) const {
+    const uint8_t *b = bytes;
+    return memcmp(b, other.bytes, sizeof(bytes)) == 0;
+}
+
+bool Mac::operator!=(Mac &other) const {
+    return !operator==(other);
+}
+
+bool operator<(const Mac &a, const Mac &b) {
+    return memcmp(a.bytes, b.bytes, sizeof(a.bytes)) < 0;
+}
+
+void Mac::copy(uint8_t &_0, uint8_t &_1, uint8_t &_2, uint8_t &_3, uint8_t &_4, uint8_t &_5) const {
+    _0 = bytes[0];
+    _1 = bytes[1];
+    _2 = bytes[2];
+    _3 = bytes[3];
+    _4 = bytes[4];
+    _5 = bytes[5];
+}
+
+Mac Mac::parseMac(string s) throw(BluetoothException) {
+    unsigned int bytes[6];
+    int count = sscanf(s.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x",
+            &bytes[0], &bytes[1], &bytes[2], &bytes[3], &bytes[4], &bytes[5]);
+
+    if (count != 6) {
+        throw BluetoothException("Unable to parse mac: " + s);
+    }
+
+    return Mac((uint8_t) bytes[0], (uint8_t) bytes[1], (uint8_t) bytes[2], (uint8_t) bytes[3], (uint8_t) bytes[4], (uint8_t) bytes[5]);
+}
+
+AttPdu::AttPdu(ByteBuffer &bytes) : bytes(bytes) {
+}
+
+AttPdu::AttPdu(ByteBuffer &bytes, AttPduType type) : bytes(bytes) {
+    bytes.write8(type);
+}
+
+AttPduType AttPdu::getType() {
+    return (AttPduType) bytes.get8(0);
+}
+
+void AttPdu::makeReadByGroupType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid) {
+    bytes.write8(AttPduType::READ_BY_GROUP_TYPE_REQ);
+    bytes.write16le(startHandle);
+    bytes.write16le(endHandle);
+    bytes.write16le(uuid.value);
+}
+
+void AttPdu::makeReadByType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid) {
+    bytes.write8(AttPduType::READ_BY_TYPE_REQ);
+    bytes.write16le(startHandle);
+    bytes.write16le(endHandle);
+    bytes.write16le(uuid.value);
+}
+
+vector<AttributeData> AttPdu::parse(ByteBuffer &bytes, AttPduType type) {
+    DF << "bytes: " << bytes.toString();
+
+    AttPduType t = (AttPduType) bytes.read8();
+
+    if (t == INVALID_HANDLE) {
+        return vector<AttributeData>();
+    }
+
+    if (t != type) {
+        throw BluetoothException("Unexpected type: " + to_string(t));
+    }
+
+    if (bytes.getSize() < 4) {
+        throw BluetoothException("Bad READ_BY_GROUP_TYPE_RES packet, expected at least 4 octets, got " + to_string(bytes.getSize()));
+    }
+
+    uint8_t length = bytes.read8();
+    D << "length=" << (int) length;
+
+    size_t count = (bytes.getSize() - 2) / length;
+    D << "count=" << count;
+
+    vector<AttributeData> values;
+    for (int i = 0; i < count; i++) {
+        auto data = bytes.view(length);
+        D << "data, size=" << data.getSize() << ", bytes=" << data.toString();
+        bytes.skip(length);
+        values.push_back(AttributeData::fromByteBuffer(data));
+    }
+
+    return values;
+}
+
+vector<AttributeData> AttPdu::parseReadByGroupType(ByteBuffer &bytes) {
+    return parse(bytes, READ_BY_GROUP_TYPE_RES);
+}
+
+vector<AttributeData> AttPdu::parseReadByType(ByteBuffer &bytes) {
+    return parse(bytes, READ_BY_TYPE_RES);
+}
+
+// -----------------------------------------------------------------------
+// AttributeData
+// -----------------------------------------------------------------------
+
+AttributeData AttributeData::fromByteBuffer(ByteBuffer &bytes) {
+    uint16_t handle = bytes.read16le();
+
+    return AttributeData(handle, bytes.view());
+}
+
+AttributeData::AttributeData(uint16_t handle, ByteBuffer value) :
+        handle(handle), value(value) {
+}
+
+AttributeData::~AttributeData() {
+}
+
+// -----------------------------------------------------------------------
+// Device
+// -----------------------------------------------------------------------
+
+BluetoothDevice::BluetoothDevice() {
+}
+
+BluetoothDevice::~BluetoothDevice() {
+}
+
+// -----------------------------------------------------------------------
+// Adapter
+// -----------------------------------------------------------------------
+
+BluetoothAdapter::BluetoothAdapter() {
+}
+
+BluetoothAdapter::~BluetoothAdapter() {
+}
+
+/*
+map<int, LinuxBluetoothAdapter *> adapters;
+
+BluetoothAdapter &getAdapter(int hciDevice) {
+    map<int, LinuxBluetoothAdapter *>::iterator it = adapters.find(hciDevice);
+
+    if (it == adapters.end()) {
+        LinuxBluetoothAdapter *adapter = new LinuxBluetoothAdapter(hciDevice);
+        adapters[hciDevice] = adapter;
+        return *adapter;
+    }
+
+    return *it->second;
+}
+*/
+
+BluetoothAdapter &getAdapter(int hciDevice) {
+    return getAdapterImpl(hciDevice);
+}
+
+void shutdown() {
+    shutdownImpl();
+}
+
+}
+};
diff --git a/ble/Bluetooth.h b/ble/Bluetooth.h
new file mode 100644
index 0000000..e47ff3e
--- /dev/null
+++ b/ble/Bluetooth.h
@@ -0,0 +1,206 @@
+#ifndef BLUETOOTH_H
+#define BLUETOOTH_H
+
+#include <string>
+#include <stdexcept>
+#include <boost/uuid/uuid.hpp>
+#include <boost/optional.hpp>
+
+#include "ByteBuffer.h"
+
+namespace trygvis {
+namespace bluetooth {
+
+using namespace std;
+
+struct SpecUuid {
+public:
+    SpecUuid(uint16_t value) : value(value) {}
+    uint16_t value;
+};
+
+namespace uuids {
+static const SpecUuid PRIMARY_SERVICE = SpecUuid(0x2800);
+static const SpecUuid SECONDARY_SERVICE = SpecUuid(0x2801);
+static const SpecUuid CHARACTERISTIC = SpecUuid(0x2803);
+}
+
+class BluetoothAdapter;
+
+class BluetoothDevice;
+
+class BluetoothGattService;
+
+class BluetoothGattCharacteristic;
+
+class BluetoothException : public runtime_error {
+public:
+    BluetoothException(const BluetoothAdapter *adapter, string const &what) :
+            runtime_error(what), adapter(adapter), device(nullptr) {
+    }
+
+    BluetoothException(const BluetoothDevice *device, string const &what) :
+            runtime_error(what), adapter(nullptr), device(device) {
+    }
+
+    BluetoothException(string const &what) :
+            runtime_error(what), adapter(nullptr), device(nullptr) {
+    }
+
+    const BluetoothAdapter *adapter;
+    const BluetoothDevice *device;
+};
+
+class Mac {
+public:
+    Mac(uint8_t _0, uint8_t _1, uint8_t _2, uint8_t _3, uint8_t _4, uint8_t _5) {
+        bytes[0] = _0;
+        bytes[1] = _1;
+        bytes[2] = _2;
+        bytes[3] = _3;
+        bytes[4] = _4;
+        bytes[5] = _5;
+    };
+
+    string str() const;
+
+    bool operator==(Mac &other) const;
+
+    bool operator!=(Mac &other) const;
+
+    void copy(uint8_t &_0, uint8_t &_1, uint8_t &_2, uint8_t &_3, uint8_t &_4, uint8_t &_5) const;
+
+    static Mac parseMac(string s) throw(BluetoothException);
+
+    friend bool operator<(const Mac &a, const Mac &b);
+
+private:
+    uint8_t bytes[6];
+};
+
+class BluetoothGattCharacteristic {
+public:
+    virtual ~BluetoothGattCharacteristic() {
+    };
+
+    virtual BluetoothGattService &getService() const = 0;
+
+    virtual uint16_t getHandle() const = 0;
+
+    virtual const boost::uuids::uuid getUuid() const = 0;
+
+    virtual uint8_t getProperties() const = 0;
+
+    virtual uint16_t getValueHandle() const = 0;
+};
+
+class BluetoothGattService {
+public:
+    virtual ~BluetoothGattService() {
+    };
+
+    virtual BluetoothDevice &getDevice() const = 0;
+
+    virtual boost::uuids::uuid getUuid() const = 0;
+
+    virtual uint16_t getHandle() const = 0;
+
+    virtual uint16_t getEndGroupHandle() const = 0;
+
+    virtual const vector<BluetoothGattCharacteristic *> getCharacteristics() const = 0;
+
+    virtual void addCharacteristic(BluetoothGattCharacteristic* characteristic) = 0;
+};
+
+class BluetoothDevice {
+public:
+    BluetoothDevice();
+
+    virtual ~BluetoothDevice();
+
+    virtual Mac const &mac() = 0;
+
+    virtual BluetoothAdapter &adapter() = 0;
+
+    virtual void connect() = 0;
+
+    virtual void disconnect() = 0;
+
+    virtual void discoverServices() = 0;
+
+    virtual const vector<BluetoothGattService *> getServices() const = 0;
+
+    virtual const boost::optional<BluetoothGattService*> findService(boost::uuids::uuid uuid) const = 0;
+};
+
+class BluetoothAdapter {
+public:
+    virtual void startScan() = 0;
+
+    virtual void stopScan() = 0;
+
+    virtual void runScan(void (callback)(BluetoothDevice &device)) = 0;
+
+    virtual BluetoothDevice &getDevice(Mac &mac) = 0;
+
+protected:
+    BluetoothAdapter();
+
+    virtual ~BluetoothAdapter();
+};
+
+enum AttPduType {
+    ERROR = 0x00,
+    INVALID_HANDLE = 0x01,
+    READ_BY_TYPE_REQ = 0x08,
+    READ_BY_TYPE_RES = 0x09,
+    READ_BY_GROUP_TYPE_REQ = 0x10,
+    READ_BY_GROUP_TYPE_RES = 0x11,
+};
+
+class AttributeData;
+
+class AttPdu {
+public:
+    AttPdu(ByteBuffer &bytes);
+
+    AttPdu(ByteBuffer &bytes, AttPduType type);
+
+    AttPduType getType();
+
+    static vector<AttributeData> parseReadByGroupType(ByteBuffer &bytes);
+
+    static vector<AttributeData> parseReadByType(ByteBuffer &bytes);
+
+    static void makeReadByGroupType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid);
+
+    static void makeReadByType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid);
+
+private:
+    static void checkType(ByteBuffer &bytes, AttPduType type);
+    static vector<AttributeData> parse(ByteBuffer &bytes, AttPduType type);
+
+    ByteBuffer &bytes;
+};
+
+class AttributeData {
+public:
+    ~AttributeData();
+
+    static AttributeData fromByteBuffer(ByteBuffer &value);
+
+    const uint16_t handle;
+    ByteBuffer value;
+
+private:
+    AttributeData(uint16_t handle, ByteBuffer value);
+};
+
+BluetoothAdapter &getAdapter(int hciDevice);
+
+void shutdown();
+
+}
+}
+
+#endif
diff --git a/ble/BluetoothImpl.h b/ble/BluetoothImpl.h
new file mode 100644
index 0000000..69fac6e
--- /dev/null
+++ b/ble/BluetoothImpl.h
@@ -0,0 +1,160 @@
+#ifndef BLUETOOTH_IMPL_H
+#define BLUETOOTH_IMPL_H
+
+#include "Bluetooth.h"
+#include <boost/uuid/uuid_io.hpp>
+#include <cstring>
+
+#define BLUETOOTH_UUID_INITIALIZER \
+    { \
+        0x00, 0x00, 0x00, 0x00, \
+        0x00, 0x00, \
+        0x10, 0x00, \
+        0x80, 0x00, \
+        0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb \
+    };
+
+namespace trygvis {
+namespace bluetooth {
+
+typedef boost::uuids::uuid uuid_t;
+template <class t>
+using o = boost::optional<t>;
+
+class DefaultBluetoothGattCharacteristic : public BluetoothGattCharacteristic {
+public:
+    DefaultBluetoothGattCharacteristic(BluetoothGattService &service, uint16_t handle, uuid_t uuid, uint8_t properties, uint16_t valueHandle)
+            : service(service), handle(handle), uuid(uuid), properties(properties), valueHandle(valueHandle) {
+    }
+
+    virtual ~DefaultBluetoothGattCharacteristic() {
+    }
+
+    BluetoothGattService &getService() const {
+        return service;
+    }
+
+    uint16_t getHandle() const {
+        return handle;
+    }
+
+    const uuid_t getUuid() const {
+        return uuid;
+    }
+
+    uint8_t getProperties() const {
+        return properties;
+    }
+
+    uint16_t getValueHandle() const {
+        return valueHandle;
+    }
+
+protected:
+    BluetoothGattService &service;
+    uint16_t handle;
+    uuid_t uuid;
+    uint8_t properties;
+    uint16_t valueHandle;
+};
+
+class DefaultBluetoothGattService : public BluetoothGattService {
+public:
+    DefaultBluetoothGattService(BluetoothDevice &device, const uuid_t uuid, const uint16_t handle, const uint16_t endGroupHandle)
+            : device(device), uuid(uuid), handle(handle), endGroupHandle(endGroupHandle) {
+        DF;
+    }
+
+    virtual ~DefaultBluetoothGattService() {
+        DF;
+        removeCharacteristics();
+    }
+
+    virtual BluetoothDevice &getDevice() const {
+        return device;
+    }
+
+    virtual uuid_t getUuid() const {
+        return uuid;
+    }
+
+    virtual uint16_t getHandle() const {
+        return handle;
+    }
+
+    virtual uint16_t getEndGroupHandle() const {
+        return endGroupHandle;
+    }
+
+    virtual const vector<BluetoothGattCharacteristic *> getCharacteristics() const {
+        return characteristics;
+    }
+
+    virtual void addCharacteristic(BluetoothGattCharacteristic *characteristic) {
+        characteristics.push_back(characteristic);
+    }
+
+protected:
+    BluetoothDevice &device;
+    const uuid_t uuid;
+    const uint16_t handle;
+    const uint16_t endGroupHandle;
+    vector<BluetoothGattCharacteristic *> characteristics;
+
+    void removeCharacteristics() {
+        DF;
+        for (auto &c: characteristics) {
+            delete c;
+        }
+        characteristics.clear();
+    }
+};
+
+class DefaultBluetoothDevice : public BluetoothDevice {
+public:
+    virtual const vector<BluetoothGattService *> getServices() const {
+        return services;
+    };
+
+    virtual const o<BluetoothGattService*> findService(boost::uuids::uuid uuid) const {
+        for (auto s: services) {
+            if (memcmp(s->getUuid().data, uuid.data, 16) == 0) {
+                return o<BluetoothGattService*>(s);
+            }
+        }
+
+        return o<BluetoothGattService*>();
+    }
+
+    virtual void addService(BluetoothGattService *service) {
+        services.push_back(service);
+    }
+
+protected:
+    DefaultBluetoothDevice() {
+        DF;
+    }
+
+    virtual ~DefaultBluetoothDevice() {
+        DF;
+        removeServices();
+    }
+
+    void removeServices() {
+        for (auto s: services) {
+            delete s;
+        }
+        services.clear();
+    }
+
+    vector<BluetoothGattService *> services;
+};
+
+BluetoothAdapter &getAdapterImpl(int hciDevice);
+
+void shutdownImpl();
+
+}
+};
+
+#endif
diff --git a/ble/ByteBuffer.cpp b/ble/ByteBuffer.cpp
new file mode 100644
index 0000000..820c638
--- /dev/null
+++ b/ble/ByteBuffer.cpp
@@ -0,0 +1,102 @@
+#include "ByteBuffer.h"
+#include <string.h>
+#include <sstream>
+#include <iomanip>
+
+using namespace std;
+
+ByteBuffer::ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity) :
+    bytes(bytes), capacity(capacity), zero(bytes.get()), end(&bytes.get()[capacity]) {
+    ptr = (uint8_t *) zero;
+}
+
+ByteBuffer::ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, size_t zero, size_t size) :
+        bytes(bytes), capacity(capacity), zero(&bytes.get()[zero]), end(&bytes.get()[size]) {
+    assert(zero <= size);
+    assert(size <= capacity);
+    ptr = (uint8_t *) zero;
+}
+
+ByteBuffer::ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, const uint8_t *zero, const uint8_t *end) :
+        bytes(bytes), capacity(capacity), zero(zero), end(end), ptr((uint8_t *) zero) {
+}
+
+ByteBuffer &ByteBuffer::write8(uint8_t value) {
+    checkAndUpdateEnd(1);
+    (*ptr++) = value;
+    return *this;
+}
+
+ByteBuffer &ByteBuffer::write16le(uint16_t value) {
+    checkAndUpdateEnd(2);
+    (*ptr++) = (uint8_t) (value & 0xff);
+    (*ptr++) = (uint8_t) ((value >> 8) & 0xff);
+    return *this;
+}
+
+uint8_t ByteBuffer::get8(size_t index) const {
+    assertCanAccessRelative(index);
+    return ptr[index];
+}
+
+uint8_t ByteBuffer::read8() {
+    assertCanAccessRelative(0);
+    return *ptr++;
+}
+
+uint16_t ByteBuffer::read16le() {
+    assertCanAccessRelative(0);
+    uint16_t value;
+    value = *ptr++;
+    value |= ((uint16_t) *ptr++) << 8;
+    return value;
+}
+
+void ByteBuffer::copy(uint8_t *bytes, size_t length) const {
+    assertCanAccessRelative(length - 1);
+
+    memcpy(bytes, ptr, length);
+}
+
+ByteBuffer ByteBuffer::view() const {
+//    DF << "cursor=" << getCursor() << ", size=" << getSize() << ", new size=" << end - ptr << ", ptr=" << (uint64_t) ptr << ", zero=" << (uint64_t) zero;
+    return view(ptr, end);
+}
+
+ByteBuffer ByteBuffer::view(size_t length) const {
+    return ByteBuffer(bytes, length, ptr, ptr + length);
+}
+
+ByteBuffer ByteBuffer::view(uint8_t *ptr, const uint8_t *end) const {
+    return ByteBuffer(bytes, end - ptr, ptr, end);
+}
+
+void ByteBuffer::checkAndUpdateEnd(size_t newBytes) {
+    uint8_t *newEnd = ptr + newBytes;
+    if (newEnd >= end) {
+        if (newEnd >= &zero[capacity]) {
+            throw ByteBufferException(string("New size is too large! cursor=") + to_string(getCursor()) + ", size=" + to_string(getSize()) + ", capacity=" + to_string(capacity) + ", new bytes=" + to_string(newBytes));
+        }
+        end = newEnd;
+    }
+}
+
+void ByteBuffer::assertCanAccessRelative(size_t diff) const {
+    assertCanAccessIndex(ptr + diff);
+}
+
+void ByteBuffer::assertCanAccessIndex(uint8_t *p) const {
+    if (p >= end || p < zero) {
+        throw ByteBufferException(string("Out of bounds! size=") + to_string(getSize()) + ", index=" + to_string(p - zero));
+    }
+}
+
+std::string ByteBuffer::toString() const {
+    stringstream s;
+
+    for (uint8_t *i = (uint8_t *) zero; i < end; i++) {
+        s << hex << setfill('0') << setw(2) << (int) *i << " ";
+    }
+
+    return string(s.str());
+}
diff --git a/ble/ByteBuffer.h b/ble/ByteBuffer.h
new file mode 100644
index 0000000..3836966
--- /dev/null
+++ b/ble/ByteBuffer.h
@@ -0,0 +1,91 @@
+#ifndef BYTE_STREAM_WRAPPER_H
+#define BYTE_STREAM_WRAPPER_H
+
+#include <cstdint>
+#include <cstdlib>
+#include <string>
+#include <stdexcept>
+
+// For now
+#include "log.h"
+
+class ByteBufferException : public std::runtime_error {
+public:
+    ByteBufferException(std::string const &what) : std::runtime_error(what) {
+    }
+};
+
+class ByteBuffer {
+public:
+    ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity);
+
+    ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, size_t zero, size_t size);
+
+    inline size_t getSize() const {
+//        DF << "end=" << (uint64_t)end << ", zero=" << (uint64_t)zero << ", size=" << (end - zero);
+        return end - zero;
+    }
+
+    inline size_t getCapacity() const {
+        return capacity;
+    }
+
+    inline size_t getCursor() const {
+        return ptr - zero;
+    }
+
+    inline size_t getBytesLeft() const {
+        return end - ptr;
+    }
+
+    inline void setCursor(size_t newCursor) {
+//        assertCanAccessRelative(newCursor);
+        ptr = (uint8_t *) &zero[newCursor];
+    }
+
+    inline void skip(size_t length) {
+//        checkAndUpdateEnd(length);
+        ptr += length;
+    }
+
+    ByteBuffer &write8(uint8_t value);
+
+    ByteBuffer &write16le(uint16_t value);
+
+    uint8_t get8(size_t index) const;
+
+    uint8_t read8();
+
+    uint16_t read16le();
+
+    void copy(uint8_t *bytes, size_t length) const;
+
+    /**
+    * Creates a view from cursor to size.
+    */
+    ByteBuffer view() const;
+
+    // TODO: should return const
+    ByteBuffer view(size_t length) const;
+
+    std::string toString() const;
+
+private:
+    ByteBuffer(const std::shared_ptr<uint8_t> bytes, size_t capacity, const uint8_t *zero, const uint8_t *end);
+
+    ByteBuffer view(uint8_t *ptr, const uint8_t *end) const;
+
+    void checkAndUpdateEnd(size_t count);
+
+    void assertCanAccessRelative(size_t diff) const;
+
+    void assertCanAccessIndex(uint8_t *p) const;
+
+    const std::shared_ptr<uint8_t> bytes;
+    const size_t capacity;
+    const uint8_t *zero;
+    const uint8_t *end;
+    uint8_t *ptr;
+};
+
+#endif
diff --git a/ble/CMakeLists.txt b/ble/CMakeLists.txt
new file mode 100644
index 0000000..1ecfb6c
--- /dev/null
+++ b/ble/CMakeLists.txt
@@ -0,0 +1,3 @@
+file(GLOB SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
+
+add_library(ble ${SOURCE_FILES})
diff --git a/ble/LinuxBluetooth.cpp b/ble/LinuxBluetooth.cpp
new file mode 100644
index 0000000..4b7f0e0
--- /dev/null
+++ b/ble/LinuxBluetooth.cpp
@@ -0,0 +1,512 @@
+#include "BluetoothImpl.h"
+
+#include <string.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <map>
+#include <sstream>
+#include <iomanip>
+#include <numeric>
+
+// Got to love magic constants. Taken from bluez.git/tools/btgatt-client.c
+#define ATT_CID 4
+
+#define MAX_MTU 256
+
+namespace trygvis {
+namespace bluetooth {
+namespace linux {
+
+typedef boost::uuids::uuid uuid_t;
+
+using namespace uuids;
+
+class LinuxBluetoothDevice;
+
+class LinuxBluetoothAdapter;
+
+class LinuxBluetoothManager;
+
+class LinuxBluetoothAdapter : public BluetoothAdapter {
+public:
+    LinuxBluetoothAdapter(int hciDeviceId);
+
+    ~LinuxBluetoothAdapter();
+
+    void runScan(void (*callback)(BluetoothDevice &device)) override;
+
+    BluetoothDevice &getDevice(Mac &mac) override;
+
+private:
+    void startScan() override;
+
+    void stopScan() override;
+
+    int hciDeviceId;
+    int hciSocket;
+    struct hci_filter hciFilter;
+    bool scanning;
+
+    map<Mac, LinuxBluetoothDevice *> devices;
+};
+
+class LinuxBluetoothDevice : public DefaultBluetoothDevice {
+public:
+    LinuxBluetoothDevice(LinuxBluetoothAdapter &adapter, Mac mac);
+
+    Mac const &mac() override;
+
+    LinuxBluetoothAdapter &adapter() override;
+
+    void connect() override;
+
+    void disconnect() override;
+
+    void discoverServices() override;
+
+private:
+    vector<AttributeData> discoverServices(uint16_t startHandle);
+
+    vector<AttributeData> discoverCharacteristics(uint16_t startHandle, uint16_t endHandle);
+
+    ByteBuffer writeAndRead(ByteBuffer &out, shared_ptr<uint8_t> buffer, size_t size);
+
+    uuid_t readUuid(const ByteBuffer &bytes) const;
+
+    LinuxBluetoothAdapter &_adapter;
+    Mac _mac;
+    int l2cap;
+};
+
+// Utilities
+
+string errnoAsString() {
+    return string(strerror(errno));
+};
+
+// -----------------------------------------------------------------------
+// Mac
+// -----------------------------------------------------------------------
+
+Mac parseMac(bdaddr_t &a) {
+    return Mac(a.b[0], a.b[1], a.b[2], a.b[3], a.b[4], a.b[5]);
+}
+
+// -----------------------------------------------------------------------
+// Device
+// -----------------------------------------------------------------------
+
+LinuxBluetoothDevice::LinuxBluetoothDevice(LinuxBluetoothAdapter &adapter, Mac mac) :
+        DefaultBluetoothDevice(), _adapter(adapter), _mac(mac) {
+}
+
+Mac const &LinuxBluetoothDevice::mac() {
+    return _mac;
+}
+
+LinuxBluetoothAdapter &LinuxBluetoothDevice::adapter() {
+    return _adapter;
+}
+
+void LinuxBluetoothDevice::connect() {
+    struct sockaddr_l2 addr;
+
+    D << "connect: mac=" << _mac.str();
+
+    l2cap = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+    if (l2cap < 0) {
+        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): socket(): " + errnoAsString());
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr.l2_family = AF_BLUETOOTH;
+    addr.l2_bdaddr = {{0, 0, 0, 0, 0, 0}};
+    addr.l2_cid = htobs(ATT_CID);
+    addr.l2_bdaddr_type = BDADDR_LE_PUBLIC;
+
+    if (bind(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+        close(l2cap);
+        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): bind(): " + errnoAsString());
+    }
+
+    struct bt_security btsec;
+    memset(&btsec, 0, sizeof(btsec));
+    btsec.level = BT_SECURITY_LOW;
+    if (setsockopt(l2cap, SOL_BLUETOOTH, BT_SECURITY, &btsec, sizeof(btsec)) != 0) {
+        close(l2cap);
+        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): setsockopt(): " + errnoAsString());
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr.l2_family = AF_BLUETOOTH;
+    addr.l2_cid = htobs(ATT_CID);
+    addr.l2_bdaddr_type = BDADDR_LE_RANDOM;
+    _mac.copy(addr.l2_bdaddr.b[5],
+            addr.l2_bdaddr.b[4],
+            addr.l2_bdaddr.b[3],
+            addr.l2_bdaddr.b[2],
+            addr.l2_bdaddr.b[1],
+            addr.l2_bdaddr.b[0]);
+
+    if (::connect(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+        close(l2cap);
+        throw BluetoothException(this, "LinuxBluetoothDevice::connect(): connect(): " + errnoAsString());
+    }
+}
+
+void LinuxBluetoothDevice::disconnect() {
+    DF << "mac = " << _mac.str();
+    close(l2cap);
+}
+
+uuid_t LinuxBluetoothDevice::readUuid(const ByteBuffer &bytes) const {
+    size_t bytesLeft = bytes.getBytesLeft();
+
+    uuid_t u;
+
+    if (bytesLeft == 2) {
+        uint8_t bs[16] = BLUETOOTH_UUID_INITIALIZER;
+        bs[2] = bytes.get8(1);
+        bs[3] = bytes.get8(0);
+        memcpy(&u, bs, 16);
+    } else if (bytesLeft == 16) {
+        uint8_t bs[16];
+        bs[15] = bytes.get8(0);
+        bs[14] = bytes.get8(1);
+        bs[13] = bytes.get8(2);
+        bs[12] = bytes.get8(3);
+        bs[11] = bytes.get8(4);
+        bs[10] = bytes.get8(5);
+        bs[9] = bytes.get8(6);
+        bs[8] = bytes.get8(7);
+        bs[7] = bytes.get8(8);
+        bs[6] = bytes.get8(9);
+        bs[5] = bytes.get8(10);
+        bs[4] = bytes.get8(11);
+        bs[3] = bytes.get8(12);
+        bs[2] = bytes.get8(13);
+        bs[1] = bytes.get8(14);
+        bs[0] = bytes.get8(15);
+        memcpy(&u, bs, 16);
+    } else {
+        throw BluetoothException(this, "Unexpected bytes left: " + to_string(bytesLeft));
+    }
+
+    return u;
+}
+
+void LinuxBluetoothDevice::discoverServices() {
+    uint16_t startHandle = 0x0001;
+
+    removeServices();
+
+    do {
+        vector<AttributeData> values = discoverServices(startHandle);
+
+        // Shouldn't happen, but you never know.
+        if (values.size() == 0) {
+            break;
+        }
+
+        uint16_t endGroupHandle;
+
+        for (auto &data: values) {
+            endGroupHandle = data.value.read16le();
+
+//            D << "handle: 0x" << hex << setw(4) << setfill('0') << data.handle <<
+//                        ", endGroupHandle: 0x" << hex << setw(4) << setfill('0') << endGroupHandle <<
+//                        ", value: " << data.value.toString();
+
+            uuid_t u = readUuid(data.value);
+
+            addService(new DefaultBluetoothGattService(*this, u, data.handle, endGroupHandle));
+        }
+
+        auto last = values.back();
+
+        startHandle = endGroupHandle;
+    } while (startHandle != 0xffff);
+
+//    for (auto &s : services) {
+//        D << "service: " << to_string(s->getUuid()) << ", handle: " << s->getHandle() << ", end group handle: " << s->getEndGroupHandle();
+//        D << "s= " << (uint64_t) s;
+//    }
+
+    auto it = services.begin(),
+            end = services.end();
+
+    if (it == end) {
+        return;
+    }
+
+    startHandle = 0x0001;
+    vector<AttributeData> chars;
+
+    auto s = *it;
+
+    do {
+        vector<AttributeData> values = discoverCharacteristics(startHandle, 0xffff);
+
+        if (values.size() == 0) {
+            break;
+        }
+
+        uint16_t lastHandle = 0xffff;
+
+        for (auto &c : values) {
+            while (c.handle > s->getEndGroupHandle() && it != end) {
+                s = *it++;
+//                D << "service: " << s->getHandle();
+            }
+
+            lastHandle = c.handle;
+
+            uint8_t properties = c.value.read8();
+            uint16_t valueHandle = c.value.read16le();
+            uuid_t uuid = readUuid(c.value);
+
+//            D << "characteristic: handle: " << setw(2) << setfill('0') << hex << (int) c.handle <<
+//                        ", properties: " << setw(2) << setfill('0') << hex << (int) properties <<
+//                        ", valueHandle: 0x" << setw(4) << setfill('0') << hex << (int) valueHandle <<
+//                        ", uuid: " << uuid;
+
+            s->addCharacteristic(new DefaultBluetoothGattCharacteristic(*s, c.handle, uuid, properties, valueHandle));
+        }
+
+        auto last = values.back();
+
+        startHandle = lastHandle + (uint8_t) 2;
+    } while (startHandle != 0xffff);
+}
+
+ByteBuffer LinuxBluetoothDevice::writeAndRead(ByteBuffer &out, shared_ptr<uint8_t> buffer, size_t size) {
+    D << "pdu size=" << out.getCursor();
+    ssize_t written = write(l2cap, buffer.get(), out.getCursor());
+
+    D << "written=" << written;
+
+    ssize_t r = read(l2cap, buffer.get(), size);
+
+    if (r == -1) {
+        throw BluetoothException(this, "read(): " + errnoAsString());
+    }
+
+    auto in = ByteBuffer(buffer, (size_t) r);
+
+    D << "read: " << r << " bytes: " << in.toString();
+
+    return in;
+}
+
+vector<AttributeData> LinuxBluetoothDevice::discoverServices(uint16_t startHandle) {
+    DF;
+
+    shared_ptr<uint8_t> buffer(new uint8_t[MAX_MTU]);
+    ByteBuffer out = ByteBuffer(buffer, MAX_MTU);
+
+    AttPdu::makeReadByGroupType(out, startHandle, 0xffff, uuids::PRIMARY_SERVICE);
+
+    ByteBuffer in = writeAndRead(out, buffer, MAX_MTU);
+
+    vector<AttributeData> values = AttPdu::parseReadByGroupType(in);
+
+    D << "READ_BY_GROUP_TYPE response has " + to_string(values.size()) + " values";
+
+    return values;
+}
+
+vector<AttributeData> LinuxBluetoothDevice::discoverCharacteristics(uint16_t startHandle, uint16_t endHandle) {
+    DF;
+
+    shared_ptr<uint8_t> buffer(new uint8_t[MAX_MTU]);
+    ByteBuffer out = ByteBuffer(buffer, MAX_MTU);
+
+    AttPdu::makeReadByType(out, startHandle, endHandle, uuids::CHARACTERISTIC);
+
+    ByteBuffer in = writeAndRead(out, buffer, MAX_MTU);
+
+    vector<AttributeData> values = AttPdu::parseReadByType(in);
+
+    D << "READ_BY_TYPE response has " + to_string(values.size()) + " values";
+
+    return values;
+}
+
+// -----------------------------------------------------------------------
+// Adapter
+// -----------------------------------------------------------------------
+
+LinuxBluetoothAdapter::LinuxBluetoothAdapter(int hciDeviceId) :
+        scanning(false) {
+    DF << "hciDeviceId=" << hciDeviceId;
+
+    this->hciDeviceId = hciDeviceId;
+    hciSocket = ::hci_open_dev(hciDeviceId);
+
+    D << "HCI socket: " << hciSocket;
+
+    if (hciSocket == -1) {
+        throw BluetoothException(this, "Could not open HCI device " + to_string(hciDeviceId));
+    }
+
+    hci_filter_clear(&hciFilter);
+    hci_filter_set_ptype(HCI_EVENT_PKT, &hciFilter);
+    hci_filter_set_event(EVT_LE_META_EVENT, &hciFilter);
+    hci_filter_set_event(EVT_LE_ADVERTISING_REPORT, &hciFilter);
+    setsockopt(hciSocket, SOL_HCI, HCI_FILTER, &hciFilter, sizeof(hciFilter));
+}
+
+LinuxBluetoothAdapter::~LinuxBluetoothAdapter() {
+    DF;
+
+    stopScan();
+
+    close(hciSocket);
+
+    for (auto &pair : devices) {
+        delete pair.second;
+    }
+}
+
+void LinuxBluetoothAdapter::startScan() {
+    DF;
+
+    struct hci_dev_info di;
+
+    if (hci_devinfo(hciDeviceId, &di) < 0) {
+        throw BluetoothException(this, "HCI adapter is not up: " + to_string(hciDeviceId));
+    }
+
+    D << "hciDeviceId.dev_id=" << di.dev_id;
+    D << "hciDeviceId.bdaddr=" << parseMac(di.bdaddr).str();
+    D << "hciDeviceId.flags=" << setw(8) << setfill('0') << hex << di.flags;
+    D << "hciDeviceId.flags RUNNING = " << hci_test_bit(HCI_RUNNING, &di.flags);
+    D << "hciDeviceId.flags UP      = " << hci_test_bit(HCI_UP, &di.flags);
+    D << "hciDeviceId.flags PSCAN   = " << hci_test_bit(HCI_PSCAN, &di.flags);
+    D << "hciDeviceId.flags ISCAN   = " << hci_test_bit(HCI_ISCAN, &di.flags);
+    D << "hciDeviceId.name=" << di.name;
+
+    int up = hci_test_bit(HCI_UP, &di.flags);
+
+    if (!up) {
+        throw BluetoothException(this, "HCI adapter is not up: " + to_string(hciDeviceId));
+    }
+
+    if (hci_le_set_scan_parameters(hciSocket, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0, 1000) < 0) {
+        throw BluetoothException(this, "hci_le_set_scan_parameters: " + errnoAsString());
+    }
+
+    if (hci_le_set_scan_enable(hciSocket, 1, 0, 1000)) {
+        throw BluetoothException(this, "Could not start scanning: other" + errnoAsString());
+    }
+
+    scanning = true;
+}
+
+void LinuxBluetoothAdapter::stopScan() {
+    DF;
+
+    if (!scanning) {
+        return;
+    }
+
+    scanning = false;
+
+    if (hci_le_set_scan_enable(hciSocket, 0, 0, 1000) < 0) {
+        W << "stopScan: hci_le_set_scan_enable: " << errnoAsString();
+    }
+}
+
+BluetoothDevice &LinuxBluetoothAdapter::getDevice(Mac &mac) {
+    map<Mac, LinuxBluetoothDevice *>::iterator it = devices.find(mac);
+
+    if (it == devices.end()) {
+        LinuxBluetoothDevice *device = new LinuxBluetoothDevice(*this, mac);
+        devices[mac] = device;
+        return *device;
+    }
+
+    return *it->second;
+}
+
+void LinuxBluetoothAdapter::runScan(void (*callback)(BluetoothDevice &device)) {
+    fd_set rfds;
+    FD_ZERO(&rfds);
+    FD_SET(hciSocket, &rfds);
+
+    startScan();
+
+    while (scanning) {
+        // Linux can change tv, so it has to be reinitialized
+        struct timeval tv;
+        tv.tv_sec = 1;
+        tv.tv_usec = 0;
+
+        int selected = select(hciSocket + 1, &rfds, NULL, NULL, &tv);
+
+        if (selected == -1) {
+            throw BluetoothException(this, "select() failed");
+        }
+
+        if (selected == 0) {
+            D << "timeout";
+            // Timeout, just continue
+            continue;
+        }
+
+        unsigned char hciEventBuf[HCI_MAX_EVENT_SIZE];
+
+        ssize_t len = read(hciSocket, hciEventBuf, sizeof(hciEventBuf));
+        evt_le_meta_event *metaEvent = (evt_le_meta_event *) (hciEventBuf + (1 + HCI_EVENT_HDR_SIZE));
+        len -= (1 + HCI_EVENT_HDR_SIZE);
+
+        D << "metaEvent->subevent = " << std::hex << (int) metaEvent->subevent;
+
+        if (metaEvent->subevent == EVT_LE_ADVERTISING_REPORT) {
+            le_advertising_info *advertisingInfo = (le_advertising_info *) (metaEvent->data + 1);
+
+            Mac mac = parseMac(advertisingInfo->bdaddr);
+
+            BluetoothDevice &device = getDevice(mac);
+
+            callback(device);
+        }
+    }
+}
+
+}
+}
+}
+
+// -----------------------------------------------------------------------
+// Implementation of platform-specific method.
+// -----------------------------------------------------------------------
+
+namespace trygvis {
+namespace bluetooth {
+using namespace trygvis::bluetooth::linux;
+
+map<int, LinuxBluetoothAdapter *> adapters;
+
+BluetoothAdapter &getAdapterImpl(int hciDevice) {
+    map<int, LinuxBluetoothAdapter *>::iterator it = adapters.find(hciDevice);
+
+    if (it == adapters.end()) {
+        LinuxBluetoothAdapter *adapter = new LinuxBluetoothAdapter(hciDevice);
+        adapters[hciDevice] = adapter;
+        return *adapter;
+    }
+
+    return *it->second;
+}
+
+void shutdownImpl() {
+    for (auto &pair: adapters) {
+        delete pair.second;
+    }
+}
+
+}
+}
diff --git a/ble/log.h b/ble/log.h
new file mode 100644
index 0000000..1d62121
--- /dev/null
+++ b/ble/log.h
@@ -0,0 +1,14 @@
+#ifndef LOG_H
+#define LOG_H
+
+#include <boost/log/core.hpp>
+#include <boost/log/trivial.hpp>
+#define D BOOST_LOG_TRIVIAL(debug)
+#define I BOOST_LOG_TRIVIAL(info)
+#define W BOOST_LOG_TRIVIAL(warning)
+
+#define DF BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": "
+#define IF BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": "
+#define WF BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": "
+
+#endif
diff --git a/log.h b/log.h
deleted file mode 100644
index 1d62121..0000000
--- a/log.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#ifndef LOG_H
-#define LOG_H
-
-#include <boost/log/core.hpp>
-#include <boost/log/trivial.hpp>
-#define D BOOST_LOG_TRIVIAL(debug)
-#define I BOOST_LOG_TRIVIAL(info)
-#define W BOOST_LOG_TRIVIAL(warning)
-
-#define DF BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": "
-#define IF BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": "
-#define WF BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": "
-
-#endif
diff --git a/main.cpp b/main.cpp
deleted file mode 100644
index 361311c..0000000
--- a/main.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-#include <exception>
-#include <iostream>
-#include <vector>
-#include <boost/uuid/uuid_io.hpp>
-#include "Bluetooth.h"
-
-using namespace std;
-using namespace trygvis::bluetooth;
-
-Mac *targetMac;
-
-void scan_callback(BluetoothDevice &device) {
-    device.adapter().stopScan();
-
-    if (device.mac() != *targetMac) {
-        cout << "found device: " << device.mac().str() << ", but not the one we want " << targetMac->str() << endl;
-        return;
-    }
-
-    cout << "Connecting to device: " << device.mac().str() << endl;
-
-    device.connect();
-
-    device.discoverServices();
-
-    vector<BluetoothGattService *> services = device.getServices();
-    cout << "Device has " << services.size() << " services" << endl;
-    
-    for (auto &s: services) {
-        const vector<BluetoothGattCharacteristic *> characteristics = s->getCharacteristics();
-
-        cout << "Service: UUID: " << s->getUuid() << ", has " << characteristics.size() << " characteristics" << endl;
-
-        for (auto &c: characteristics) {
-            cout << "Characteristic: UUID: " << c->getUuid() << ", properties: " << (int) c->getProperties() << endl;
-        }
-    }
-
-    device.disconnect();
-}
-
-int main(int argc, char *argv[]) {
-    if (argc != 2) {
-        cerr << "usage: " << argv[0] << " [mac]" << endl;
-        return EXIT_FAILURE;
-    }
-
-    int e;
-//    try {
-    Mac mac = Mac::parseMac(argv[1]);
-    targetMac = &mac;
-
-    BluetoothAdapter &adapter = getAdapter(0);
-
-    BluetoothDevice &device = adapter.getDevice(mac);
-
-    scan_callback(device);
-
-//        adapter->runScan(scan_callback);
-
-    e = EXIT_SUCCESS;
-//    } catch (std::runtime_error ex) {
-//        W << "std::runtime_error: " << ex.what();
-//        e = EXIT_FAILURE;
-//    } catch (std::exception ex) {
-//        W << "std::exception: " << ex.what();
-//        e = EXIT_FAILURE;
-//    }
-
-    shutdown();
-    return e;
-}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..14dcdf7
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+enable_testing()
+find_package(Boost COMPONENTS log unit_test_framework REQUIRED)
+
+# If we can change directory here add_definition and test-specific stuff could be moved to the test directory
+file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *Test.cpp)
+add_definitions(-DBOOST_TEST_DYN_LINK)
+
+foreach(testSrc ${TEST_SRCS})
+    #Extract the filename without an extension (NAME_WE)
+    get_filename_component(testName ${testSrc} NAME_WE)
+
+    #Add compile target
+    add_executable(${testName} ${testSrc})
+
+    include_directories("${PROJECT_SOURCE_DIR}/ble")
+    add_dependencies(${testName} ble)
+    target_link_libraries(${testName} ble)
+    target_link_libraries(${testName} pthread)
+    target_link_libraries(${testName} ${Boost_LIBRARIES})
+
+    #I like to move testing binaries into a testBin directory
+    set_target_properties(${testName} PROPERTIES
+        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test)
+
+    #Finally add it to test execution -
+    #Notice the WORKING_DIRECTORY and COMMAND
+    add_test(NAME ${testName}
+             WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/testBin
+             COMMAND ${CMAKE_BINARY_DIR}/testBin/${testName})
+endforeach(testSrc)
-- 
cgit v1.2.3