From f4afc04ee7a085a89ad84ba89344bb50ca9e6e04 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 21 Jun 2015 12:27:48 +0200 Subject: o Trying to make the Bluetooth API more C++ idiomatic, a GATT connection has the same lifecycle as a BluetoothGatt. sm-get-value: o Better error handling. --- apps/SoilMoisture.cpp | 14 ++-- apps/SoilMoisture.h | 6 +- apps/ble-inspect-device.h | 8 +- apps/generate.cpp | 8 +- apps/launcher.cpp | 12 ++- apps/sm-get-value.h | 180 +++++++++++++++++++--------------------- ble/CMakeLists.txt | 2 +- ble/LinuxBluetooth.cpp | 62 ++++++++------ include/ble/Bluetooth.h | 6 +- sensor/include/trygvis/sensor.h | 4 +- 10 files changed, 157 insertions(+), 145 deletions(-) diff --git a/apps/SoilMoisture.cpp b/apps/SoilMoisture.cpp index 89c2fe5..d1d8b7c 100644 --- a/apps/SoilMoisture.cpp +++ b/apps/SoilMoisture.cpp @@ -62,10 +62,10 @@ ByteBuffer createSetUpdateInterval(uint8_t sensor, uint8_t interval_in_seconds) write8(interval_in_seconds); } -SoilMoisture SoilMoisture::create(BluetoothGatt &gatt) { - gatt.discoverServices(); +SoilMoisture SoilMoisture::create(shared_ptr gatt) { + gatt->discoverServices(); - auto service = gatt.findService(soil_moisture_service); + auto service = gatt->findService(soil_moisture_service); if (!service) { throw runtime_error("The device is missing the soil moisture service"); @@ -80,8 +80,8 @@ SoilMoisture SoilMoisture::create(BluetoothGatt &gatt) { return SoilMoisture(gatt, *service, *c); } -SoilMoisture::SoilMoisture(BluetoothGatt &gatt, BluetoothGattService &s, BluetoothGattCharacteristic &c) : - gatt(gatt), s(s), c(c) { +SoilMoisture::SoilMoisture(shared_ptr gatt, BluetoothGattService &s, BluetoothGattCharacteristic &c) : + gatt(std::move(gatt)), s(s), c(c) { } ByteBuffer SoilMoisture::writeAndRead(ByteBuffer &requestBytes) { @@ -89,9 +89,9 @@ ByteBuffer SoilMoisture::writeAndRead(ByteBuffer &requestBytes) { uint8_t expectedCode = requestBytes.get8(0); - gatt.writeValue(c, requestBytes); + gatt->writeValue(c, requestBytes); - auto responseBytes = gatt.readValue(c); + auto responseBytes = gatt->readValue(c); if (responseBytes.getSize() < 1) { throw runtime_error("Unexpected number of bytes read: " + to_string(requestBytes.getSize())); diff --git a/apps/SoilMoisture.h b/apps/SoilMoisture.h index 2016c9a..938da07 100644 --- a/apps/SoilMoisture.h +++ b/apps/SoilMoisture.h @@ -24,18 +24,18 @@ extern const boost::uuids::uuid soil_moisture_characteristic; class SoilMoisture { public: - static SoilMoisture create(BluetoothGatt &gatt); + static SoilMoisture create(shared_ptr gatt); uint8_t getSensorCount(); uint16_t getValue(uint8_t sensor); private: - SoilMoisture(BluetoothGatt &gatt, BluetoothGattService &s, BluetoothGattCharacteristic &c); + SoilMoisture(shared_ptr gatt, BluetoothGattService &s, BluetoothGattCharacteristic &c); ByteBuffer writeAndRead(ByteBuffer &requestBytes); - BluetoothGatt &gatt; + shared_ptr gatt; BluetoothGattService &s; BluetoothGattCharacteristic &c; }; diff --git a/apps/ble-inspect-device.h b/apps/ble-inspect-device.h index bf79b2c..7d93f95 100644 --- a/apps/ble-inspect-device.h +++ b/apps/ble-inspect-device.h @@ -28,11 +28,11 @@ public: void scan_callback(BluetoothDevice &device) { cout << "Inspecting device: " << device.getMac().str() << endl; - auto &gatt = device.connectGatt(); + auto gatt = device.connectGatt(); - gatt.discoverServices(); + gatt->discoverServices(); - vector < BluetoothGattService * > services = gatt.getServices(); + vector < BluetoothGattService * > services = gatt->getServices(); cout << "Device has " << services.size() << " services" << endl; for (auto &s: services) { @@ -46,8 +46,6 @@ public: endl; } } - - gatt.disconnect(); } int main(app_execution &execution) override { diff --git a/apps/generate.cpp b/apps/generate.cpp index ca6f8de..9dcc271 100644 --- a/apps/generate.cpp +++ b/apps/generate.cpp @@ -46,7 +46,10 @@ int main(int argc, char *argv[]) { bool first = true; - out << "template" << endl + out << "namespace trygvis {" << endl + << "namespace apps {" << endl + << endl + << "template" << endl << "int launch_app(int argc, const char *argv[]);" << endl << endl; @@ -67,6 +70,9 @@ int main(int argc, char *argv[]) { out << " } else {" << endl << " return EXIT_FAILURE;" << endl << " }" << endl + << "}" << endl + << endl + << "}" << endl << "}" << endl; return EXIT_SUCCESS; diff --git a/apps/launcher.cpp b/apps/launcher.cpp index 4c1f687..2a1e039 100644 --- a/apps/launcher.cpp +++ b/apps/launcher.cpp @@ -2,9 +2,10 @@ #include "apps-list.gen.h" #include #include -#include -using namespace trygvis::apps; +namespace trygvis { +namespace apps { + using namespace std; const po::options_description logging_options() { @@ -16,7 +17,7 @@ const po::options_description logging_options() { void setup_logging(string app_name) { Appender *console = new ConsoleAppender(true, true); - string pattern = string("%-5p ") /*"%6r "*/ + app_name + "/%-20c %m%n"; + string pattern = string("%-5p ") /*"%6r "*/ + app_name + "/%-20c %m%n"; // add %M (function name) PatternLayout *layout = new PatternLayout(LOG4CPLUS_TEXT(pattern)); console->setLayout(auto_ptr(layout)); @@ -74,6 +75,11 @@ int launch_app(int argc, const char *argv[]) { return EXIT_FAILURE; } } +} +} + +using namespace std; +using namespace trygvis::apps; int main(int argc, const char *argv[]) { if (argc == 0) { diff --git a/apps/sm-get-value.h b/apps/sm-get-value.h index 11e797f..4083f8e 100644 --- a/apps/sm-get-value.h +++ b/apps/sm-get-value.h @@ -6,9 +6,11 @@ #include "ble/Bluetooth.h" #include "SoilMoisture.h" #include "trygvis/sensor.h" -#include "json.hpp" #include "apps.h" +namespace trygvis { +namespace apps { + // I'm lazy using namespace std; using namespace std::chrono; @@ -18,92 +20,6 @@ using namespace trygvis::sensor; using namespace trygvis::sensor::io; using json = nlohmann::json; -bool loop; -sample_format_type format; -time_point targetTime; -unsigned int sleepTime; -vector sensors; - -void withConnection(sample_format_type format, BluetoothGatt &gatt) { - SoilMoisture soilMoisture = SoilMoisture::create(gatt); - - const int sensorCount = soilMoisture.getSensorCount(); - - if (sensorCount == 0) { - throw runtime_error("Sensor count is 0"); - } - - // If the user didn't specify any sensors, add all. - if (sensors.size() == 0) { - for (unsigned int i = 0; i < sensorCount; i++) { - sensors.push_back(i); - } - } - - auto mac = gatt.getDevice().getMac(); - - targetTime = system_clock::now(); - - do { - KeyDictionary dict; - auto hostname_key = dict.indexOf("hostname"); - auto device_key = dict.indexOf("device"); - auto sensor_key = dict.indexOf("sensor"); - auto timestamp_key = dict.indexOf("timestamp"); - auto value_key = dict.indexOf("value"); - - auto unique_output_stream = open_sample_output_stream(shared_ptr(&cout, noop_deleter), dict, format); - shared_ptr output_stream{std::move(unique_output_stream)}; - - for (auto sensor : sensors) { - if (sensor >= sensorCount) { - // Ignore invalid sensors - continue; - } - - auto epoch = system_clock::now().time_since_epoch(); - auto timestamp = duration_cast(epoch).count(); - uint16_t value = soilMoisture.getValue((uint8_t) sensor); - - SampleRecord sample(dict); - - sample.set(hostname_key, get_hostname()); - sample.set(device_key, mac.str()); - sample.set(sensor_key, std::to_string(sensor)); - sample.set(timestamp_key, std::to_string(timestamp)); - sample.set(value_key, std::to_string(value)); - - output_stream->write(sample); - -// if (format == sample_format_type::KEY_VALUE) { -// cout << "device=" << device.str() -// << ", sensor=" << to_string(sensor) -// << ", timestamp=" << to_string(timestamp) -// << ", value=" << (int) value << endl; -// } else if (format == sample_format_type::JSON) { -// json j; -// j["device"] = device.str(); -// j["sensor"] = sensor; -// j["timestamp"] = timestamp; -// j["value"] = value; -// cout << j << endl; -// } else if (format == sample_format_type::SQL) { -// cout << "INSERT INTO soil_moisture_sample(device, sensor, timestamp, value) VALUES(" -// << "'" << device.str() << "', " -// << sensor << ", " -// << timestamp << ", " -// << value << ";" << endl; -// } - } - - targetTime = targetTime + seconds(sleepTime); - this_thread::sleep_until(targetTime); - } while (loop); -} - -namespace trygvis { -namespace apps { - class sm_get_value : public app { public: @@ -112,15 +28,21 @@ public: ~sm_get_value() = default; + bool loop; + sample_format_type format; + unsigned int sleepTime; + vector sensors; + void add_options(po::options_description_easy_init &options) override { auto default_sleep = po::value<>(&sleepTime)->default_value(0); + auto default_format = po::value(&format)->default_value(sample_format_type::KEY_VALUE); options ("device", po::value()->required(), "MAC of device to poll") ("sensor", po::value>(&sensors)->multitoken(), "Sensor to poll, defaults to all") - ("sleep", default_sleep, "How long to sleep in seconds between each poll. If not given, it will exit after first poll") - ("format", po::value(&format)->default_value(sample_format_type::KEY_VALUE), - "Output format"); + ("sleep", default_sleep, + "How long to sleep in seconds between each poll. If not given, it will exit after first poll") + ("format", default_format, "Output format"); } int main(app_execution &execution) override { @@ -148,15 +70,21 @@ public: loop = sleepTime > 0; do { - LOG4CPLUS_INFO(execution.logger, "Connecting to device: " + device.getMac().str()); - - auto &gatt = device.connectGatt(); try { + LOG4CPLUS_INFO(execution.logger, "Connecting to device: " + device.getMac().str()); + auto gatt = device.connectGatt(); + withConnection(format, gatt); } catch (runtime_error &e) { - cout << "exception: " << e.what() << endl; + LOG4CPLUS_ERROR(execution.logger, "Exception: " << e.what()); + } + + if (loop) { + LOG4CPLUS_DEBUG(execution.logger, "Sleeping for " + std::to_string(sleepTime) + " seconds after failure."); + + auto targetTime = system_clock::now() + seconds(sleepTime); + this_thread::sleep_until(targetTime); } - gatt.disconnect(); } while (loop); return EXIT_SUCCESS; @@ -168,6 +96,68 @@ public: return EXIT_FAILURE; } } + + void withConnection(sample_format_type format, shared_ptr gatt) { + SoilMoisture soilMoisture = SoilMoisture::create(gatt); + + const int sensorCount = soilMoisture.getSensorCount(); + + if (sensorCount == 0) { + throw runtime_error("Sensor count is 0"); + } + + // If the user didn't specify any sensors, add all. + if (sensors.size() == 0) { + for (unsigned int i = 0; i < sensorCount; i++) { + sensors.push_back(i); + } + } + + auto mac = gatt->getDevice().getMac(); + + KeyDictionary dict; + auto hostname_key = dict.indexOf("hostname"); + auto device_key = dict.indexOf("device"); + auto sensor_key = dict.indexOf("sensor"); + auto timestamp_key = dict.indexOf("timestamp"); + auto value_key = dict.indexOf("value"); + + time_point targetTime; + targetTime = system_clock::now(); + + do { + auto unique_output_stream = open_sample_output_stream(shared_ptr(&cout, noop_deleter), dict, + format); + shared_ptr output_stream{std::move(unique_output_stream)}; + + auto epoch = system_clock::now().time_since_epoch(); + auto timestamp = duration_cast(epoch).count(); + + for (auto sensor : sensors) { + if (sensor >= sensorCount) { + // Ignore invalid sensors + continue; + } + + uint16_t value = soilMoisture.getValue((uint8_t) sensor); + + auto sample = SampleRecord(dict) + .set(hostname_key, get_hostname()) + .set(device_key, mac.str()) + .set(sensor_key, std::to_string(sensor)) + .set(timestamp_key, std::to_string(timestamp)) + .set(value_key, std::to_string(value)); + + output_stream->write(sample); + } + + do { + targetTime = targetTime + seconds(sleepTime); + } while (targetTime < system_clock::now()); + + this_thread::sleep_until(targetTime); + } while (loop); + } }; } diff --git a/ble/CMakeLists.txt b/ble/CMakeLists.txt index 405f125..543e85d 100644 --- a/ble/CMakeLists.txt +++ b/ble/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) +file(GLOB SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp *.h) add_library(ble ${SOURCE_FILES}) include_directories("${PROJECT_SOURCE_DIR}/include") diff --git a/ble/LinuxBluetooth.cpp b/ble/LinuxBluetooth.cpp index ae8f984..6e3c9da 100644 --- a/ble/LinuxBluetooth.cpp +++ b/ble/LinuxBluetooth.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include // Got to love magic constants. Taken from bluez.git/tools/btgatt-client.c @@ -56,10 +55,10 @@ public: ~LinuxBluetoothDevice(); - BluetoothGatt &connectGatt() override; + shared_ptr connectGatt() override; private: - LinuxBluetoothGatt *gatt; + weak_ptr gatt; int l2cap; }; @@ -69,9 +68,7 @@ public: ~LinuxBluetoothGatt(); - void connect() override; - - void disconnect() override; + bool isConnected() const override; void discoverServices() override; @@ -80,6 +77,10 @@ public: ByteBuffer readValue(const BluetoothGattCharacteristic &c) override; private: + void connect(); + + void disconnect(); + vector discoverServices(uint16_t startHandle); vector discoverCharacteristics(uint16_t startHandle, uint16_t endHandle); @@ -87,6 +88,8 @@ private: ByteBuffer writeAndRead(ByteBuffer &out, shared_ptr buffer, size_t size); int l2cap; + + bool connected; }; // Utilities @@ -108,23 +111,24 @@ Mac parseMac(bdaddr_t &a) { // ----------------------------------------------------------------------- LinuxBluetoothDevice::LinuxBluetoothDevice(LinuxBluetoothAdapter &adapter, Mac &mac) : - DefaultBluetoothDevice(adapter, mac), gatt(nullptr) { + DefaultBluetoothDevice(adapter, mac) { } LinuxBluetoothDevice::~LinuxBluetoothDevice() { - if (gatt) { - delete gatt; - } +// if (gatt) { +// delete gatt; +// } }; -BluetoothGatt &LinuxBluetoothDevice::connectGatt() { - if (!gatt) { - gatt = new LinuxBluetoothGatt(*this, l2cap); +shared_ptr LinuxBluetoothDevice::connectGatt() { + if (auto p = gatt.lock()) { + return p; } + auto ref = make_shared(*this, l2cap); - gatt->connect(); + gatt = ref; - return *gatt; + return ref; } // ----------------------------------------------------------------------- @@ -133,9 +137,15 @@ BluetoothGatt &LinuxBluetoothDevice::connectGatt() { LinuxBluetoothGatt::LinuxBluetoothGatt(LinuxBluetoothDevice &device, int l2cap) : DefaultBluetoothGatt(device), l2cap(l2cap) { + connect(); } LinuxBluetoothGatt::~LinuxBluetoothGatt() { + disconnect(); +} + +bool LinuxBluetoothGatt::isConnected() const { + return connected; } void LinuxBluetoothGatt::connect() { @@ -145,7 +155,7 @@ void LinuxBluetoothGatt::connect() { l2cap = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (l2cap < 0) { - throw BluetoothException(&device, "LinuxBluetoothGatt::connect(): socket(): " + errnoAsString()); + throw BluetoothException(&device, "connect(): socket(): " + errnoAsString()); } memset(&addr, 0, sizeof(addr)); @@ -156,7 +166,7 @@ void LinuxBluetoothGatt::connect() { if (bind(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(l2cap); - throw BluetoothException(&device, "LinuxBluetoothGatt::connect(): bind(): " + errnoAsString()); + throw BluetoothException(&device, "bind(): " + errnoAsString()); } struct bt_security btsec; @@ -164,7 +174,7 @@ void LinuxBluetoothGatt::connect() { btsec.level = BT_SECURITY_LOW; if (setsockopt(l2cap, SOL_BLUETOOTH, BT_SECURITY, &btsec, sizeof(btsec)) != 0) { close(l2cap); - throw BluetoothException(&device, "LinuxBluetoothGatt::connect(): setsockopt(): " + errnoAsString()); + throw BluetoothException(&device, "setsockopt(): " + errnoAsString()); } memset(&addr, 0, sizeof(addr)); @@ -181,11 +191,14 @@ void LinuxBluetoothGatt::connect() { if (::connect(l2cap, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(l2cap); - throw BluetoothException(&device, "LinuxBluetoothGatt::connect(): connect(): " + errnoAsString()); + throw BluetoothException(&device, "connect(): " + errnoAsString()); } } void LinuxBluetoothGatt::disconnect() { + if (!connected) { + return; + } LOG_DEBUG("mac = " << device.getMac().str()); close(l2cap); } @@ -271,7 +284,7 @@ void LinuxBluetoothGatt::discoverServices() { break; } - uint16_t endGroupHandle; + uint16_t endGroupHandle = startHandle; for (auto &data: values) { endGroupHandle = data.value.read16le(); @@ -285,7 +298,7 @@ void LinuxBluetoothGatt::discoverServices() { addService(new DefaultBluetoothGattService(device, u, data.handle, endGroupHandle)); } - auto last = values.back(); +// auto last = values.back(); startHandle = endGroupHandle; } while (startHandle != 0xffff); @@ -303,7 +316,6 @@ void LinuxBluetoothGatt::discoverServices() { } startHandle = 0x0001; - vector chars; auto s = *it; @@ -343,10 +355,10 @@ void LinuxBluetoothGatt::discoverServices() { } ByteBuffer LinuxBluetoothGatt::writeAndRead(ByteBuffer &out, shared_ptr buffer, size_t size) { - LOG_DEBUG("pdu size=" << out.getCursor()); +// LOG_DEBUG("pdu size=" << out.getCursor()); ssize_t written = write(l2cap, buffer.get(), out.getCursor()); - LOG_DEBUG("written=" << written); +// LOG_DEBUG("written=" << written); ssize_t r = read(l2cap, buffer.get(), size); @@ -356,7 +368,7 @@ ByteBuffer LinuxBluetoothGatt::writeAndRead(ByteBuffer &out, shared_ptr auto in = ByteBuffer(buffer, (size_t) r, (size_t) r); - LOG_DEBUG("read: " << r << " bytes: " << in.toString()); +// LOG_DEBUG("read: " << r << " bytes: " << in.toString()); return in; } diff --git a/include/ble/Bluetooth.h b/include/ble/Bluetooth.h index 1a1394e..80729af 100644 --- a/include/ble/Bluetooth.h +++ b/include/ble/Bluetooth.h @@ -124,9 +124,7 @@ public: virtual BluetoothDevice &getDevice() const = 0; - virtual void connect() = 0; - - virtual void disconnect() = 0; + virtual bool isConnected() const = 0; virtual void writeValue(const BluetoothGattCharacteristic &c, const ByteBuffer &bytes) = 0; @@ -149,7 +147,7 @@ public: virtual BluetoothAdapter &getAdapter() = 0; - virtual BluetoothGatt &connectGatt() = 0; + virtual shared_ptr connectGatt() = 0; }; class BluetoothAdapter { diff --git a/sensor/include/trygvis/sensor.h b/sensor/include/trygvis/sensor.h index bcbd43b..48fb509 100644 --- a/sensor/include/trygvis/sensor.h +++ b/sensor/include/trygvis/sensor.h @@ -184,10 +184,12 @@ public: return values.at(index); } - void set(const SampleKey *key, const std::string &value) { + SampleRecord& set(const SampleKey *key, const std::string &value) { values.resize(max(values.size(), key->index + 1)); values.at(key->index).reset(value); + + return *this; } template -- cgit v1.2.3