aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2016-04-12 20:06:47 +0200
committerTrygve Laugstøl <trygvis@inamo.no>2016-04-12 20:06:47 +0200
commit2ca532122d60cff4dbdc7f24fbc5783bcc5ad68d (patch)
tree19f9ce796886e216a608fa5e938bd2bd4f0d0b55 /apps
parentce07550c57172443c10a66957b50085e273d20b3 (diff)
downloadble-toys-2ca532122d60cff4dbdc7f24fbc5783bcc5ad68d.tar.gz
ble-toys-2ca532122d60cff4dbdc7f24fbc5783bcc5ad68d.tar.bz2
ble-toys-2ca532122d60cff4dbdc7f24fbc5783bcc5ad68d.tar.xz
ble-toys-2ca532122d60cff4dbdc7f24fbc5783bcc5ad68d.zip
Soil Moisture: Adding support for controlling lights.
Bluetooth: refectorying, trying to be more c++ idiomatic and modern. SM/Diller: adding bluetooth to Diller bridge.
Diffstat (limited to 'apps')
-rw-r--r--apps/CMakeLists.txt80
-rw-r--r--apps/SoilMoisture.cpp50
-rw-r--r--apps/SoilMoisture.h26
-rw-r--r--apps/apps.cpp2
-rw-r--r--apps/apps.h69
-rw-r--r--apps/ble-inspect-device.cpp8
-rw-r--r--apps/ble-scan.cpp4
-rw-r--r--apps/diller_util.cpp19
-rw-r--r--apps/diller_util.h14
-rw-r--r--apps/mqtt_support.h72
-rw-r--r--apps/sm-diller.cpp423
-rw-r--r--apps/sm-get-value.cpp12
12 files changed, 680 insertions, 99 deletions
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index 34fbd30..98c9915 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -1,21 +1,3 @@
-list(APPEND APPS ble-inspect-device)
-list(APPEND APPS ble-scan)
-list(APPEND APPS sample-add-timestamp)
-list(APPEND APPS sample-convert)
-list(APPEND APPS sample-select)
-list(APPEND APPS sm-db-insert)
-list(APPEND APPS sm-db-select)
-list(APPEND APPS sm-get-value)
-list(APPEND APPS sm-serial-read)
-list(APPEND APPS sm-serial-read-all)
-
-list(APPEND INCLUDE_DIRECTORIES
- ${CMAKE_CURRENT_SOURCE_DIR}
- "${PROJECT_SOURCE_DIR}/include"
- "${PROJECT_SOURCE_DIR}/json/src"
- "${PROJECT_SOURCE_DIR}/gsl/include"
- "${PROJECT_SOURCE_DIR}/sensor/include")
-
#### Find all packages
function(find_header_and_lib PREFIX HEADER_NAME LIBRARY_NAME)
@@ -70,8 +52,6 @@ list(APPEND LIBRARIES "${LOG4CPLUS_LIBRARY}")
find_header_and_lib(MOSQUITTO mosquitto.h mosquitto)
if(MOSQUITTO_OK STREQUAL "OK")
- list(APPEND APPS mqtt-publish)
- set(mqtt-publish_SOURCES mqtt_support.cpp mqtt_support.h)
list(APPEND INCLUDE_DIRECTORIES "${MOSQUITTO_INCLUDE_DIRECTORY}")
list(APPEND LIBRARIES "${MOSQUITTO_LIBRARY}")
else()
@@ -81,26 +61,56 @@ endif()
message(STATUS "Dependency summary:")
message(STATUS "Log4cplus: -I${LOG4CPLUS_INCLUDE_DIRECTORY}, -L${LOG4CPLUS_LIBRARY}")
message(STATUS "Mosquitto: -I${MOSQUITTO_INCLUDE_DIRECTORY}, -L${MOSQUITTO_LIBRARY}")
-message(STATUS "Mosquittopp: -I${MOSQUITTOPP_INCLUDE_DIRECTORY}, -L${MOSQUITTOPP_LIBRARY}")
add_library(apps OBJECT
apps.cpp apps.h
- SoilMoisture.cpp SoilMoisture.h)
-target_include_directories(apps PUBLIC ${INCLUDE_DIRECTORIES})
+ SoilMoisture.cpp SoilMoisture.h
+ diller_util.cpp diller_util.h)
+target_include_directories(apps PUBLIC
+ "${PROJECT_SOURCE_DIR}/include"
+ "${PROJECT_SOURCE_DIR}/json/src"
+ )
-foreach(app ${APPS})
- add_executable(${app} ${app}.cpp $<TARGET_OBJECTS:apps> ${${app}_SOURCES})
+function(add_app)
+ cmake_parse_arguments(ADD_APP "" "NAME" "EXTRA" ${ARGN})
- target_include_directories(${app} PUBLIC ${INCLUDE_DIRECTORIES})
+ set(app "${ADD_APP_NAME}")
- target_link_libraries(${app}
- ble
- trygvis-sensor
- ${LIBRARIES}
- ${Boost_LIBRARIES}
- ${BLUEZ_LIBRARIES}
- ${PQXX_LIBRARIES}
- ${CMAKE_THREAD_LIBS_INIT})
+ add_executable(${app} ${app}.cpp $<TARGET_OBJECTS:apps> "${ADD_APP_EXTRA}")
+
+ target_include_directories(${app} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ "${PROJECT_SOURCE_DIR}/include"
+ "${PROJECT_SOURCE_DIR}/json/src"
+ "${PROJECT_SOURCE_DIR}/gsl/include"
+ "${PROJECT_SOURCE_DIR}/sensor/include"
+ )
+ target_link_libraries(${app}
+ ble
+ trygvis-sensor
+ ${LIBRARIES}
+ ${Boost_LIBRARIES}
+ ${BLUEZ_LIBRARIES}
+ ${PQXX_LIBRARIES}
+ ${CMAKE_THREAD_LIBS_INIT}
+ )
install(TARGETS ${app} RUNTIME DESTINATION bin)
-endforeach()
+endfunction()
+
+add_app(NAME ble-inspect-device)
+add_app(NAME ble-scan)
+add_app(NAME sample-add-timestamp)
+add_app(NAME sample-convert)
+add_app(NAME sample-select)
+add_app(NAME sm-db-insert)
+add_app(NAME sm-db-select)
+add_app(NAME sm-get-value)
+add_app(NAME sm-serial-read)
+add_app(NAME sm-serial-read-all)
+
+if(MOSQUITTO_OK STREQUAL "OK")
+ set(MQTT_SOURCES mqtt_support.cpp mqtt_support.h)
+ add_app(NAME mqtt-publish EXTRA "${MQTT_SOURCES}")
+ add_app(NAME sm-diller EXTRA "${MQTT_SOURCES}")
+endif()
diff --git a/apps/SoilMoisture.cpp b/apps/SoilMoisture.cpp
index 13578f6..4b9dfe5 100644
--- a/apps/SoilMoisture.cpp
+++ b/apps/SoilMoisture.cpp
@@ -16,6 +16,7 @@ auto bluetooth_base_uuid =
const boost::uuids::uuid soil_moisture_service = makeUuid(trygvis_io_base_uuid, 0x00, 0x10);
const boost::uuids::uuid soil_moisture_characteristic = makeUuid(trygvis_io_base_uuid, 0x00, 0x11);
const boost::uuids::uuid temperature_characteristic = makeUuid(bluetooth_base_uuid, 0x2a, 0x1e);
+const boost::uuids::uuid light_characteristic = makeUuid(trygvis_io_base_uuid, 0x00, 0x12);
using namespace trygvis::bluetooth;
@@ -64,13 +65,15 @@ ByteBuffer createSetUpdateInterval(uint8_t sensor, uint8_t interval_in_seconds)
SoilMoisture SoilMoisture::create(shared_ptr<BluetoothGatt> gatt) {
gatt->discoverServices();
- auto service = gatt->findService(soil_moisture_service);
+ o<shared_ptr<BluetoothGattService>> s = gatt->findService(soil_moisture_service);
- if (!service) {
+ if (!s) {
throw runtime_error("The device is missing the soil moisture service");
}
- auto c = service->findCharacteristic(soil_moisture_characteristic);
+ shared_ptr<BluetoothGattService> &service = *s;
+
+ o<shared_ptr<BluetoothGattCharacteristic>> c = service->findCharacteristic(soil_moisture_characteristic);
if (!c) {
throw runtime_error("The device is missing the soil moisture characteristic");
@@ -78,16 +81,20 @@ SoilMoisture SoilMoisture::create(shared_ptr<BluetoothGatt> gatt) {
auto temperature = service->findCharacteristic(temperature_characteristic);
- return SoilMoisture(gatt, *service, c.get(), temperature);
+ auto light = service->findCharacteristic(light_characteristic);
+
+ return SoilMoisture(gatt, service, c.operator*(), temperature, light);
}
-SoilMoisture::SoilMoisture(const shared_ptr<BluetoothGatt> &gatt, BluetoothGattService &s,
- const BluetoothGattCharacteristic &soilMoistureCharacteristic,
- const o<const BluetoothGattCharacteristic &> temperatureCharacteristic)
+SoilMoisture::SoilMoisture(const shared_ptr<BluetoothGatt> &gatt,
+ const shared_ptr<BluetoothGattService> &s,
+ const BluetoothGattCharacteristicPtr &soilMoistureCharacteristic,
+ const o<BluetoothGattCharacteristicPtr> temperatureCharacteristic,
+ const o<BluetoothGattCharacteristicPtr> lightCharacteristic)
: gatt(gatt), s(s), soilMoistureCharacteristic(soilMoistureCharacteristic),
- temperatureCharacteristic(temperatureCharacteristic) {}
+ temperatureCharacteristic(temperatureCharacteristic), lightCharacteristic(lightCharacteristic) { }
-ByteBuffer SoilMoisture::writeAndRead(const BluetoothGattCharacteristic &c, ByteBuffer &requestBytes) {
+ByteBuffer SoilMoisture::writeAndRead(const BluetoothGattCharacteristicPtr &c, ByteBuffer &requestBytes) {
requestBytes.setCursor(0);
uint8_t expectedCode = requestBytes.get8(0);
@@ -131,14 +138,15 @@ string SoilMoisture::getName(uint8_t sensor) {
buffer.copy(bytes, bytesLeft);
if (bytesLeft == 0 || bytesLeft < (bytes[0] + 1)) {
- throw runtime_error("Bad response from device. buffer size: " + to_string(bytesLeft) + ", buffer[0]=" + to_string((int)bytes[0]));
+ throw runtime_error("Bad response from device. buffer size: " + to_string(bytesLeft) + ", buffer[0]=" +
+ to_string((int) bytes[0]));
}
- return string((const char *)&bytes[1], bytes[0]);
+ return string((const char *) &bytes[1], bytes[0]);
}
bool SoilMoisture::hasTemperatureSensor() {
- return temperatureCharacteristic.is_initialized();
+ return (bool) temperatureCharacteristic;
}
/**
@@ -149,9 +157,7 @@ o<double> SoilMoisture::readTemperature() {
return o<double>();
}
- auto &c = temperatureCharacteristic.get();
-
- auto responseBytes = gatt->readValue(c);
+ auto responseBytes = gatt->readValue(*temperatureCharacteristic);
if (responseBytes.getSize() < 2) {
throw runtime_error("Unexpected number of bytes read: " + to_string(responseBytes.getSize()));
@@ -168,5 +174,19 @@ o<double> SoilMoisture::readTemperature() {
return o<double>(temperature);
}
+void SoilMoisture::setLight(uint8_t light, uint8_t value) {
+ if (!lightCharacteristic) {
+ return;
+ }
+
+ auto responseBytes = gatt->readValue(*lightCharacteristic);
+
+ if (responseBytes.getSize() < 2) {
+ throw runtime_error("Unexpected number of bytes read: " + to_string(responseBytes.getSize()));
+ }
+
+ bitset<8> b0 = responseBytes.read8();
+}
+
}
}
diff --git a/apps/SoilMoisture.h b/apps/SoilMoisture.h
index 99b698d..0ad1d15 100644
--- a/apps/SoilMoisture.h
+++ b/apps/SoilMoisture.h
@@ -2,13 +2,14 @@
#include <ble/Bluetooth.h>
#include <boost/uuid/uuid.hpp>
+#include <experimental/optional>
namespace trygvis {
namespace sensor {
using namespace trygvis::bluetooth;
-template <typename T>
-using o = boost::optional<T>;
+template<typename T>
+using o = std::experimental::optional<T>;
enum class sm_cmd_code : uint8_t {
SM_CMD_GET_SENSOR_COUNT = 1,
@@ -18,6 +19,8 @@ enum class sm_cmd_code : uint8_t {
SM_CMD_SET_SENSOR_NAME = 5,
SM_CMD_GET_SENSOR_NAME = 6,
SM_CMD_SET_UPDATE_INTERVAL = 7,
+ // 8 is used
+ SM_CMD_SET_LIGHT = 9,
SM_CMD_FAIL = 255,
};
@@ -38,17 +41,22 @@ public:
o<double> readTemperature();
+ void setLight(uint8_t light, uint8_t value);
+
private:
- SoilMoisture(const shared_ptr<BluetoothGatt> &gatt, BluetoothGattService &s,
- const BluetoothGattCharacteristic &soilMoistureCharacteristic,
- const o<const BluetoothGattCharacteristic &> temperatureCharacteristic);
+ SoilMoisture(const shared_ptr<BluetoothGatt> &gatt,
+ const shared_ptr<BluetoothGattService> &s,
+ const shared_ptr<BluetoothGattCharacteristic> &soilMoistureCharacteristic,
+ const o<BluetoothGattCharacteristicPtr> temperatureCharacteristic,
+ const o<BluetoothGattCharacteristicPtr> lightCharacteristic);
- ByteBuffer writeAndRead(const BluetoothGattCharacteristic &c, ByteBuffer &requestBytes);
+ ByteBuffer writeAndRead(const BluetoothGattCharacteristicPtr &c, ByteBuffer &requestBytes);
shared_ptr<BluetoothGatt> gatt;
- BluetoothGattService &s;
- const BluetoothGattCharacteristic &soilMoistureCharacteristic;
- const o<const BluetoothGattCharacteristic &> temperatureCharacteristic;
+ shared_ptr<BluetoothGattService> s;
+ const BluetoothGattCharacteristicPtr soilMoistureCharacteristic;
+ const o<BluetoothGattCharacteristicPtr> temperatureCharacteristic;
+ const o<BluetoothGattCharacteristicPtr> lightCharacteristic;
};
}
}
diff --git a/apps/apps.cpp b/apps/apps.cpp
index b21d27e..56abc7b 100644
--- a/apps/apps.cpp
+++ b/apps/apps.cpp
@@ -56,7 +56,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"; // add %M (function name)
+ string pattern = "%-5p %D{%Y-%m-%d %H:%M:%S} " + app_name + "/%-20c %m%n"; // add %M (function name)
PatternLayout *layout = new PatternLayout(LOG4CPLUS_TEXT(pattern));
console->setLayout(auto_ptr<Layout>(layout));
diff --git a/apps/apps.h b/apps/apps.h
index f3d6eae..9173ae0 100644
--- a/apps/apps.h
+++ b/apps/apps.h
@@ -8,6 +8,8 @@
#include <iosfwd>
#include <experimental/optional>
#include <json.hpp>
+#include <mutex>
+#include <condition_variable>
namespace trygvis {
namespace apps {
@@ -22,12 +24,61 @@ using json = nlohmann::json;
class missing_key : public std::runtime_error {
public:
- missing_key(const json::string_t key) : runtime_error("Missing key: " + key), key(key) {}
+ missing_key(const json::string_t key) : runtime_error("Missing key: " + key), key(key) { }
const json::string_t key;
};
-template <typename T>
+class waitable {
+protected:
+ std::condition_variable cv;
+ std::mutex mutex;
+
+ waitable() {
+ }
+
+ virtual ~waitable() {
+ }
+
+public:
+ void wait() {
+ std::unique_lock<std::mutex> lock(mutex);
+ cv.wait(lock);
+ }
+
+ template<class Predicate>
+ void wait(Predicate predicate) {
+ std::unique_lock<std::mutex> lock(mutex);
+ cv.wait(lock, predicate);
+ }
+
+ template<class Rep, class Period>
+ std::cv_status wait_for(const std::chrono::duration<Rep, Period> &rel_time) {
+ std::unique_lock<std::mutex> lock(mutex);
+ return cv.wait_for(lock, rel_time);
+ }
+
+ template<class Rep, class Period, class Predicate>
+ bool wait_for(const std::chrono::duration<Rep, Period> &rel_time, Predicate predicate) {
+ std::unique_lock<std::mutex> lock(mutex);
+ return cv.wait_for(lock, rel_time, predicate);
+ }
+
+ template<class Clock, class Duration>
+ std::cv_status wait_until(const std::chrono::time_point<Clock, Duration> &timeout_time) {
+ std::unique_lock<std::mutex> lock(mutex);
+ return cv.wait_until(lock, timeout_time);
+ };
+
+ template<class Clock, class Duration, class Predicate>
+ bool wait_until(const std::chrono::time_point<Clock, Duration> &timeout_time,
+ Predicate predicate) {
+ std::unique_lock<std::mutex> lock(mutex);
+ return cv.wait_until(lock, timeout_time, predicate);
+ }
+};
+
+template<typename T>
T get(json j, std::string key) {
auto ref = j[key];
@@ -41,7 +92,7 @@ T get(json j, std::string key) {
class app_execution {
public:
app_execution(po::options_description desc, po::variables_map vm, Logger logger)
- : desc(desc), vm(vm), logger(logger) {}
+ : desc(desc), vm(vm), logger(logger) { }
const po::options_description desc;
const po::variables_map vm;
@@ -52,7 +103,7 @@ public:
class app {
public:
- app(std::string app_name) : app_name(app_name) {}
+ app(std::string app_name) : app_name(app_name) { }
app(const app &) = delete;
@@ -62,9 +113,9 @@ public:
app &operator=(const app &) = delete;
- virtual void add_options(po::options_description_easy_init &options) {}
+ virtual void add_options(po::options_description_easy_init &options) { }
- virtual void add_extra_options(po::options_description &options) {}
+ virtual void add_extra_options(po::options_description &options) { }
virtual int main(app_execution &execution) = 0;
@@ -75,12 +126,12 @@ std::string get_hostname();
int real_main(app *app, int argc, const char *argv[]);
-template <typename T>
+template<typename T>
class noop_delete {
public:
- void operator()(T *) const {}
+ void operator()(T *) const { }
};
-static inline void noop_deleter(void *) {}
+static inline void noop_deleter(void *) { }
}
}
diff --git a/apps/ble-inspect-device.cpp b/apps/ble-inspect-device.cpp
index 2140e03..baee93a 100644
--- a/apps/ble-inspect-device.cpp
+++ b/apps/ble-inspect-device.cpp
@@ -29,11 +29,11 @@ public:
gatt->discoverServices();
- vector<BluetoothGattService *> services = gatt->getServices();
+ auto services = gatt->getServices();
cout << "Device has " << services.size() << " services" << endl;
for (auto &s : services) {
- const vector<BluetoothGattCharacteristic *> characteristics = s->getCharacteristics();
+ auto characteristics = s->getCharacteristics();
cout << "Service: UUID: " << s->getUuid() << ", has " << characteristics.size() << " characteristics"
<< endl;
@@ -60,10 +60,10 @@ public:
scan_callback(device);
return EXIT_SUCCESS;
- } catch (std::runtime_error ex) {
+ } catch (std::runtime_error &ex) {
cout << "std::runtime_error: " << ex.what() << endl;
return EXIT_FAILURE;
- } catch (std::exception ex) {
+ } catch (std::exception &ex) {
cout << "std::exception: " << ex.what() << endl;
return EXIT_FAILURE;
}
diff --git a/apps/ble-scan.cpp b/apps/ble-scan.cpp
index 158af14..b148752 100644
--- a/apps/ble-scan.cpp
+++ b/apps/ble-scan.cpp
@@ -67,10 +67,10 @@ public:
for_each(begin(seen_devices), end(seen_devices), [&](auto mac) { cout << mac.str() << endl; });
return EXIT_SUCCESS;
- } catch (std::runtime_error ex) {
+ } catch (std::runtime_error &ex) {
cout << "std::runtime_error: " << ex.what() << endl;
return EXIT_FAILURE;
- } catch (std::exception ex) {
+ } catch (std::exception &ex) {
cout << "std::exception: " << ex.what() << endl;
return EXIT_FAILURE;
}
diff --git a/apps/diller_util.cpp b/apps/diller_util.cpp
new file mode 100644
index 0000000..420ebc6
--- /dev/null
+++ b/apps/diller_util.cpp
@@ -0,0 +1,19 @@
+#include "diller_util.h"
+
+#include <string>
+
+namespace trygvis {
+namespace diller {
+
+using namespace std;
+
+string diller_topic_for_property_value(const string &device, const string &property) {
+ return "/diller/" + device + "/property/" + property + "/value";
+}
+
+string diller_topic_for_property_name(const string &device, const string &property) {
+ return "/diller/" + device + "/property/" + property + "/name";
+}
+
+}; // namespace diller
+}; // namespace trygvis
diff --git a/apps/diller_util.h b/apps/diller_util.h
new file mode 100644
index 0000000..c7b74f2
--- /dev/null
+++ b/apps/diller_util.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <string>
+
+namespace trygvis {
+namespace diller {
+
+using std::string;
+
+string diller_topic_for_property_value(const string &device, const string &property);
+string diller_topic_for_property_name(const string &device, const string &property);
+
+}; // namespace diller
+}; // namespace trygvis
diff --git a/apps/mqtt_support.h b/apps/mqtt_support.h
index 48a6c39..ab319fd 100644
--- a/apps/mqtt_support.h
+++ b/apps/mqtt_support.h
@@ -2,10 +2,10 @@
#define TRYGVIS_MQTT_SUPPORT_H
#include <mutex>
+#include <shared_mutex>
#include <string>
#include <exception>
#include <cstring>
-#include <span.h>
#include <log4cplus/logger.h>
#include <log4cplus/loggingmacros.h>
#include <atomic>
@@ -13,16 +13,14 @@
#include <limits.h>
#include <experimental/optional>
#include "mosquitto.h"
+#include "apps.h"
namespace trygvis {
namespace mqtt_support {
-template<typename T>
-using o = std::experimental::optional<T>;
-
+using namespace trygvis::apps;
using namespace std;
using namespace log4cplus;
-using namespace gsl;
static inline
string error_to_string(int rc) {
@@ -32,6 +30,26 @@ string error_to_string(int rc) {
return string(mosquitto_strerror(rc));
}
+static
+vector<string> mqtt_tokenize_topic(string path) {
+ char **topics;
+ int topic_count;
+ int i;
+
+ mosquitto_sub_topic_tokenise(path.c_str(), &topics, &topic_count);
+
+ vector<string> res;
+ for (i = 0; i < topic_count; i++) {
+ if (topics[i] != NULL) {
+ res.emplace_back(topics[i]);
+ }
+ }
+
+ mosquitto_sub_topic_tokens_free(&topics, topic_count);
+
+ return res;
+}
+
class mqtt_error : public std::runtime_error {
public:
@@ -79,7 +97,7 @@ enum mqtt_client_personality {
};
template<mqtt_client_personality personality>
-class mqtt_client : private mqtt_lib {
+class mqtt_client : public waitable, private mqtt_lib {
template<bool>
struct personality_tag {
};
@@ -101,8 +119,8 @@ class mqtt_client : private mqtt_lib {
// bool should_reconnect_;
int unacked_messages_;
- condition_variable cv;
- mutex cv_mutex;
+// condition_variable cv;
+// mutex cv_mutex;
void assert_success(const string &function, int rc) {
if (rc != MOSQ_ERR_SUCCESS) {
@@ -113,9 +131,20 @@ class mqtt_client : private mqtt_lib {
public:
mqtt_client(const string &host, const int port, const int keep_alive, const o<string> &client_id,
const bool clean_session) :
- host(host), port(port), connecting_(false), connected_(false), /*should_reconnect_(false),*/
- keep_alive(keep_alive), unacked_messages_(0) {
- mosquitto = mosquitto_new(client_id ? (*client_id).c_str() : nullptr, clean_session, this);
+ host(host), port(port), connecting_(false), connected_(false), /*should_reconnect_(false),*/
+ keep_alive(keep_alive), unacked_messages_(0) {
+ const char *id = nullptr;
+
+ if (client_id) {
+ id = client_id->c_str();
+ }
+ else {
+ if (!clean_session) {
+ throw mqtt_error("If client id is not specified, clean session must be true", MOSQ_ERR_INVAL);
+ }
+ }
+
+ mosquitto = mosquitto_new(id, clean_session, this);
if (!mosquitto) {
string err = strerror(errno);
throw runtime_error("Could not initialize mosquitto instance: " + err);
@@ -162,11 +191,6 @@ private:
}
public:
- void wait() {
- unique_lock<mutex> lk(cv_mutex);
- cv.wait(lk);
- }
-
int unacked_messages() {
guard lock(this_mutex);
return unacked_messages_;
@@ -235,7 +259,7 @@ private:
void on_disconnect_wrapper(int rc) {
guard lock(this_mutex);
- LOG4CPLUS_INFO(logger, "Disconnected");
+ LOG4CPLUS_INFO(logger, "Disconnected, rc=" << error_to_string(rc));
bool was_connecting = connecting_, was_connected = connected_;
connecting_ = connected_ = false;
@@ -320,6 +344,18 @@ public:
assert_success("mosquitto_subscribe", rc);
}
+ void publish(int *mid, const string &topic, int qos, bool retain, const string &s) {
+ auto len = s.length();
+
+ auto int_max = std::numeric_limits<int>::max();
+
+ if (len > int_max) {
+ len = static_cast<decltype(len)>(int_max);
+ }
+
+ publish(mid, topic, qos, retain, static_cast<int>(len), s.c_str());
+ }
+
void publish(int *mid, const string &topic, int qos, bool retain, int payload_len, const void *payload) {
// if (!connected_) {
// throw mqtt_error("not connected", MOSQ_ERR_NO_CONN);
@@ -329,7 +365,7 @@ public:
int rc = mosquitto_publish(mosquitto, mid, topic.c_str(), payload_len, payload, qos, retain);
- if(rc == MOSQ_ERR_SUCCESS) {
+ if (rc == MOSQ_ERR_SUCCESS) {
guard lock(this_mutex);
unacked_messages_++;
}
diff --git a/apps/sm-diller.cpp b/apps/sm-diller.cpp
new file mode 100644
index 0000000..2b1d63d
--- /dev/null
+++ b/apps/sm-diller.cpp
@@ -0,0 +1,423 @@
+#include "ble/Bluetooth.h"
+#include "SoilMoisture.h"
+#include "diller_util.h"
+#include "trygvis/sensor.h"
+#include "trygvis/sensor/io.h"
+#include "apps.h"
+#include "mqtt_support.h"
+
+#include <boost/uuid/uuid_io.hpp>
+#include <log4cplus/loggingmacros.h>
+#include <iostream>
+#include <iomanip>
+#include <chrono>
+#include <list>
+#include <thread>
+#include <typeinfo>
+
+namespace trygvis {
+namespace apps {
+
+// I'm lazy
+using namespace std;
+using namespace std::chrono;
+using namespace trygvis::apps;
+using namespace trygvis::bluetooth;
+using namespace trygvis::sensor;
+using namespace trygvis::sensor::io;
+using namespace trygvis::mqtt_support;
+using namespace trygvis::diller;
+
+static struct {
+ string server;
+ string client_id;
+ int sleep_time;
+} opts;
+
+struct device_command {
+ virtual ~device_command() {
+ }
+};
+
+struct set_light final : public virtual device_command {
+ set_light(uint8_t light, uint8_t value) : light(light), value(value) {
+ }
+
+ virtual ~set_light() {
+ }
+
+ const uint8_t light;
+ const uint8_t value;
+};
+
+struct diller_command {
+ diller_command() {
+ cerr << "diller_command" << endl;
+ }
+
+ virtual ~diller_command() {
+ cerr << "~diller_command" << endl;
+ }
+};
+
+struct publish_soil_moisture_value final : public virtual diller_command {
+ publish_soil_moisture_value(uint8_t sensor, int value) : sensor(sensor), value(value) {
+ cerr << "publish_soil_moisture_value, this=" << (this) << endl;
+ }
+
+ virtual ~publish_soil_moisture_value() {
+ cerr << "~publish_soil_moisture_value, this=" << (this) << endl;
+ }
+
+ const int sensor;
+ const int value;
+};
+
+struct publish_soil_moisture_sensor_name final : public virtual diller_command {
+ publish_soil_moisture_sensor_name(uint8_t sensor, string name) : sensor(sensor), name(name) {
+ }
+
+ virtual ~publish_soil_moisture_sensor_name() {
+ }
+
+ const int sensor;
+ const string name;
+};
+
+atomic<bool> loop{true};
+
+mutex main_mutex;
+condition_variable main_cv;
+std::list<unique_ptr<device_command>> to_device;
+std::list<unique_ptr<diller_command>> to_diller;
+
+class diller_mqtt_client : public mqtt_client<polling> {
+public:
+ diller_mqtt_client(const Mac mac, const string &host, const int port, const int keep_alive,
+ const o<string> &client_id, const bool clean_session) :
+ mqtt_client(host, port, keep_alive, client_id, clean_session), mac(mac),
+ light_path(diller_topic_for_property_value(mac.str(), "light")) {
+ }
+
+ void run() {
+ connect();
+
+ while (loop) {
+ poll();
+
+ std::unique_lock<std::mutex> lock(main_mutex);
+ auto res = main_cv.wait_for(lock, 1s);
+
+ if (res == cv_status::no_timeout) {
+ if (!to_diller.empty()) {
+ diller_command &command = *to_diller.front();
+
+ if (typeid(command) == typeid(struct publish_soil_moisture_value &)) {
+ auto cmd = dynamic_cast<struct publish_soil_moisture_value &>(command);
+
+ LOG4CPLUS_DEBUG(logger, "Publishing soil moisture value: sensor=" << cmd.sensor
+ << ", value=" << cmd.value);
+
+ int mid;
+
+ const string topic = diller_topic_for_property_value(mac.str(), "soil-moisture-" +
+ std::to_string(cmd.sensor));
+ auto qos = 0;
+ publish(&mid, topic, qos, false, std::to_string(cmd.value));
+
+ LOG4CPLUS_DEBUG(logger, "Published as " << mid);
+ } else if (typeid(command) == typeid(struct publish_soil_moisture_sensor_name &)) {
+ auto cmd = dynamic_cast<struct publish_soil_moisture_sensor_name &>(command);
+
+ LOG4CPLUS_DEBUG(logger, "Publishing soil moisture sensor name: sensor=" << cmd.sensor
+ << ", name=" << cmd.name);
+
+ int mid;
+
+ const string topic = diller_topic_for_property_name(mac.str(), "soil-moisture-" +
+ std::to_string(cmd.sensor));
+ auto qos = 0;
+ publish(&mid, topic, qos, true, cmd.name);
+
+ LOG4CPLUS_DEBUG(logger, "Published as " << mid);
+ }
+
+ to_diller.pop_front();
+ }
+ }
+ }
+
+ LOG4CPLUS_INFO(logger, "Stopping");
+
+ disconnect();
+
+ LOG4CPLUS_INFO(logger, "Stopped");
+ }
+
+protected:
+ void on_connect(int rc) override {
+ if (rc == MOSQ_ERR_SUCCESS) {
+ LOG4CPLUS_INFO(logger, "Subscribing to " << light_path);
+ subscribe(nullptr, light_path, 0);
+ }
+ }
+
+ void on_message(const struct mosquitto_message *message) override {
+ LOG4CPLUS_INFO(logger, "got message: " << message->topic);
+
+ auto segments = mqtt_tokenize_topic(message->topic);
+
+ // /diller/aa:bb:cc:dd:ee:ff/property/light/value
+ if (segments.size() != 5 ||
+ segments[0] != "diller" ||
+ // $mac
+ segments[2] != "property" ||
+ // $property
+ segments[4] != "value") {
+ LOG4CPLUS_INFO(logger, "Unknown message topic: " << message->topic);
+ return;
+ }
+
+ if (message->payloadlen != 1) {
+ LOG4CPLUS_WARN(logger, "Unknown message payload, expected exactly one byte, got " << message->payloadlen <<
+ " bytes on topic " << message->topic);
+ return;
+ }
+
+ auto property_name = segments[3];
+
+ if (property_name == "light") {
+ std::unique_lock<std::mutex> lock(main_mutex);
+ uint8_t value = static_cast<uint8_t *>(message->payload)[0];
+
+ const int light = 0;
+
+ LOG4CPLUS_INFO(logger,
+ "Pushing command set_light(light=" << light << ", value=" << static_cast<int>(value) << ")");
+
+ to_device.push_back(make_unique<set_light>(light, value));
+ main_cv.notify_all();
+ } else {
+ LOG4CPLUS_INFO(logger, "Unknown property name " << property_name);
+ }
+ }
+
+private:
+ const Mac mac;
+ const string light_path;
+
+ Logger logger = Logger::getInstance(LOG4CPLUS_TEXT("diller_mqtt_client"));
+};
+
+void mqtt_thread(Mac &mac) {
+ Logger logger = Logger::getInstance(LOG4CPLUS_TEXT("mqtt_thread"));
+
+ try {
+ diller_mqtt_client mqtt_client(mac, opts.server, 1883, 60, o<string>(opts.client_id), true);
+
+ mqtt_client.run();
+ } catch (std::runtime_error &ex) {
+ LOG4CPLUS_WARN(logger, "std::exception: " << ex.what());
+ } catch (std::exception &ex) {
+ LOG4CPLUS_WARN(logger, "std::exception: " << ex.what());
+ }
+}
+
+class sm_diller : public app {
+public:
+ sm_diller() : app("sm-diller") { }
+
+ ~sm_diller() = default;
+
+ std::chrono::duration<int> sleep_time;
+ vector<uint8_t> sensorIndexes;
+ vector<pair<uint8_t, string>> sensors;
+
+ void add_options(po::options_description_easy_init &options) override {
+ auto opt_server = po::value<string>(&opts.server)->required();
+ auto opt_client_id = po::value<string>(&opts.client_id)->default_value("sm-diller-" + get_hostname());
+ auto default_sleep = po::value<>(&opts.sleep_time)->default_value(10);
+
+ options("server", opt_server, "MQTT server");
+ options("client-id", opt_client_id, "MQTT client id");
+
+ options("device", po::value<string>()->required(), "MAC of device to poll");
+ options("sensor", po::value<>(&sensorIndexes)->multitoken(),
+ "Sensor to poll, defaults to all");
+ options("sleep", default_sleep,
+ "How long to sleep in seconds between each poll. If not given, it will exit after first poll");
+ }
+
+ int main(app_execution &execution) override {
+ BluetoothSystem bluetoothSystem;
+
+ auto desc = execution.desc;
+ auto vm = execution.vm;
+
+ std::thread mqtt_t;
+ int ret;
+
+ try {
+ if (!vm.count("device")) {
+ cerr << "Missing required option: device" << endl;
+ cerr << desc << "\n";
+ return EXIT_FAILURE;
+ }
+
+ auto mac_string = vm["device"].as<string>();
+
+ Mac mac = Mac::parseMac(mac_string);
+
+ auto adapter = bluetoothSystem.getAdapter("0");
+
+ auto device = adapter->getDevice(mac);
+
+ sleep_time = std::chrono::seconds(opts.sleep_time);
+
+ mqtt_t = std::thread(mqtt_thread, std::ref(mac));
+
+ do {
+ try {
+ LOG4CPLUS_INFO(execution.logger, "Connecting to device: " << device->getMac().str());
+ auto gatt = device->connectGatt();
+
+ withConnection(execution.logger, gatt);
+ } catch (BluetoothException &e) {
+ LOG4CPLUS_ERROR(execution.logger, "Bluetooth error: " << e.what());
+ } catch (runtime_error &e) {
+ LOG4CPLUS_ERROR(execution.logger, "Exception: " << e.what());
+ }
+
+ LOG4CPLUS_DEBUG(execution.logger, "Sleeping for " << std::to_string(sleep_time.count()));
+
+ this_thread::sleep_until(system_clock::now() + sleep_time);
+ } while (loop);
+
+ unique_lock <mutex> lock(main_mutex);
+ main_cv.notify_all();
+
+ ret = EXIT_SUCCESS;
+ } catch (std::runtime_error &ex) {
+ LOG4CPLUS_WARN(execution.logger, "std::runtime_error: " << ex.what());
+ ret = EXIT_FAILURE;
+ } catch (std::exception &ex) {
+ LOG4CPLUS_WARN(execution.logger, "std::exception: " << ex.what());
+ ret = EXIT_FAILURE;
+ }
+
+ if (mqtt_t.joinable()) {
+ mqtt_t.join();
+ }
+
+ return ret;
+ }
+
+ void read_sensors(SoilMoisture &soilMoisture, string mac) {
+ auto epoch = system_clock::now().time_since_epoch();
+ auto timestamp = duration_cast<seconds>(epoch).count();
+
+ auto tempO = soilMoisture.readTemperature();
+ if (tempO) {
+ tempO.value();
+ }
+
+ for (auto s : sensors) {
+ auto sensor = s.first;
+ auto name = s.second;
+
+ uint16_t value = soilMoisture.getValue(sensor);
+
+ {
+ unique_lock <mutex> lock(main_mutex);
+ to_diller.push_back(make_unique<publish_soil_moisture_value>(sensor, value));
+ }
+ }
+
+ main_cv.notify_all();
+ }
+
+ void withConnection(const Logger &logger, shared_ptr<BluetoothGatt> gatt) {
+ SoilMoisture soilMoisture = SoilMoisture::create(gatt);
+
+ if (sensors.empty()) {
+ 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 (uint8_t i = 0; i < sensorCount; i++) {
+ sensorIndexes.push_back(i);
+ }
+ }
+
+ for_each(begin(sensorIndexes), end(sensorIndexes), [&](uint8_t i) {
+ if (i >= sensorCount) {
+ // Ignore invalid sensors
+ return;
+ }
+
+ auto name = soilMoisture.getName(i);
+ sensors.push_back(make_pair(i, name));
+
+ unique_lock <mutex> lock(main_mutex);
+ to_diller.push_back(make_unique<publish_soil_moisture_sensor_name>(i, name));
+ });
+
+ main_cv.notify_all();
+ }
+
+ auto mac = gatt->getDevice().getMac().str();
+ if (!loop) {
+ read_sensors(soilMoisture, mac);
+ } else {
+ std::chrono::time_point<system_clock> target_time;
+ target_time = system_clock::now();
+
+ do {
+ target_time = target_time + seconds(sleep_time);
+
+ read_sensors(soilMoisture, mac);
+
+ do {
+ unique_lock <mutex> lock(main_mutex);
+ auto res = main_cv.wait_until(lock, target_time);
+
+ if (res == cv_status::no_timeout) {
+ LOG4CPLUS_DEBUG(logger, "no_timeout, to_device.size()=" << to_device.size());
+ }
+
+ if (!to_device.empty()) {
+ device_command &command = *std::move(to_device.front());
+ to_device.pop_front();
+ if (typeid(command) == typeid(set_light &)) {
+ auto sl = dynamic_cast<const set_light &>(command);
+ LOG4CPLUS_DEBUG(logger, "performing Set light: light=" << static_cast<int>(sl.light)
+ << ", value=" << static_cast<int>(sl.value));
+
+ try {
+ soilMoisture.setLight(sl.light, sl.value);
+ } catch (std::runtime_error &e) {
+ LOG4CPLUS_WARN(logger,
+ "Could not execute setLight, light=" << static_cast<int>(sl.light)
+ << ", value=" << static_cast<int>(sl.value)
+ << ", error: " << e.what());
+ }
+ }
+ }
+ } while (target_time > system_clock::now());
+ } while (loop);
+ }
+ }
+};
+
+}
+}
+
+int main(int argc, const char *argv[]) {
+ using app_t = trygvis::apps::sm_diller;
+
+ return real_main(new app_t(), argc, argv);
+}
diff --git a/apps/sm-get-value.cpp b/apps/sm-get-value.cpp
index 0572155..42f5da3 100644
--- a/apps/sm-get-value.cpp
+++ b/apps/sm-get-value.cpp
@@ -35,9 +35,9 @@ public:
vector<pair<unsigned int, string>> sensors;
KeyDictionary dict;
- SampleKey *hostname_key = dict.indexOf("hostname");
- SampleKey *device_key = dict.indexOf("device");
- SampleKey *timestamp_key = dict.indexOf("timestamp_ms");
+ const SampleKey *hostname_key = dict.indexOf("hostname");
+ const SampleKey *device_key = dict.indexOf("device");
+ const SampleKey *timestamp_key = dict.indexOf("timestamp_ms");
void add_options(po::options_description_easy_init &options) override {
auto default_sleep = po::value<>(&sleepTime)->default_value(0);
@@ -96,10 +96,10 @@ public:
} while (loop);
return EXIT_SUCCESS;
- } catch (std::runtime_error ex) {
+ } catch (std::runtime_error &ex) {
cout << "std::runtime_error: " << ex.what() << endl;
return EXIT_FAILURE;
- } catch (std::exception ex) {
+ } catch (std::exception &ex) {
cout << "std::exception: " << ex.what() << endl;
return EXIT_FAILURE;
}
@@ -118,7 +118,7 @@ public:
auto tempO = soilMoisture.readTemperature();
if (tempO) {
- sample.set(dict.indexOf("temperature"), std::to_string(tempO.get()));
+ sample.set(dict.indexOf("temperature"), std::to_string(tempO.value()));
}
int i = 0;