From 2ca532122d60cff4dbdc7f24fbc5783bcc5ad68d Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Tue, 12 Apr 2016 20:06:47 +0200 Subject: Soil Moisture: Adding support for controlling lights. Bluetooth: refectorying, trying to be more c++ idiomatic and modern. SM/Diller: adding bluetooth to Diller bridge. --- apps/CMakeLists.txt | 80 +++++---- apps/SoilMoisture.cpp | 50 ++++-- apps/SoilMoisture.h | 26 ++- apps/apps.cpp | 2 +- apps/apps.h | 69 +++++++- apps/ble-inspect-device.cpp | 8 +- apps/ble-scan.cpp | 4 +- apps/diller_util.cpp | 19 ++ apps/diller_util.h | 14 ++ apps/mqtt_support.h | 72 ++++++-- apps/sm-diller.cpp | 423 ++++++++++++++++++++++++++++++++++++++++++++ apps/sm-get-value.cpp | 12 +- 12 files changed, 680 insertions(+), 99 deletions(-) create mode 100644 apps/diller_util.cpp create mode 100644 apps/diller_util.h create mode 100644 apps/sm-diller.cpp (limited to 'apps') 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 $ ${${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 $ "${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 gatt) { gatt->discoverServices(); - auto service = gatt->findService(soil_moisture_service); + o> 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 &service = *s; + + o> 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 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 &gatt, BluetoothGattService &s, - const BluetoothGattCharacteristic &soilMoistureCharacteristic, - const o temperatureCharacteristic) +SoilMoisture::SoilMoisture(const shared_ptr &gatt, + const shared_ptr &s, + const BluetoothGattCharacteristicPtr &soilMoistureCharacteristic, + const o temperatureCharacteristic, + const o 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 SoilMoisture::readTemperature() { return o(); } - 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 SoilMoisture::readTemperature() { return o(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 #include +#include namespace trygvis { namespace sensor { using namespace trygvis::bluetooth; -template -using o = boost::optional; +template +using o = std::experimental::optional; 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 readTemperature(); + void setLight(uint8_t light, uint8_t value); + private: - SoilMoisture(const shared_ptr &gatt, BluetoothGattService &s, - const BluetoothGattCharacteristic &soilMoistureCharacteristic, - const o temperatureCharacteristic); + SoilMoisture(const shared_ptr &gatt, + const shared_ptr &s, + const shared_ptr &soilMoistureCharacteristic, + const o temperatureCharacteristic, + const o lightCharacteristic); - ByteBuffer writeAndRead(const BluetoothGattCharacteristic &c, ByteBuffer &requestBytes); + ByteBuffer writeAndRead(const BluetoothGattCharacteristicPtr &c, ByteBuffer &requestBytes); shared_ptr gatt; - BluetoothGattService &s; - const BluetoothGattCharacteristic &soilMoistureCharacteristic; - const o temperatureCharacteristic; + shared_ptr s; + const BluetoothGattCharacteristicPtr soilMoistureCharacteristic; + const o temperatureCharacteristic; + const o 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)); 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 #include #include +#include +#include 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 +class waitable { +protected: + std::condition_variable cv; + std::mutex mutex; + + waitable() { + } + + virtual ~waitable() { + } + +public: + void wait() { + std::unique_lock lock(mutex); + cv.wait(lock); + } + + template + void wait(Predicate predicate) { + std::unique_lock lock(mutex); + cv.wait(lock, predicate); + } + + template + std::cv_status wait_for(const std::chrono::duration &rel_time) { + std::unique_lock lock(mutex); + return cv.wait_for(lock, rel_time); + } + + template + bool wait_for(const std::chrono::duration &rel_time, Predicate predicate) { + std::unique_lock lock(mutex); + return cv.wait_for(lock, rel_time, predicate); + } + + template + std::cv_status wait_until(const std::chrono::time_point &timeout_time) { + std::unique_lock lock(mutex); + return cv.wait_until(lock, timeout_time); + }; + + template + bool wait_until(const std::chrono::time_point &timeout_time, + Predicate predicate) { + std::unique_lock lock(mutex); + return cv.wait_until(lock, timeout_time, predicate); + } +}; + +template 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 +template 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 services = gatt->getServices(); + auto services = gatt->getServices(); cout << "Device has " << services.size() << " services" << endl; for (auto &s : services) { - const vector 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 + +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 + +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 +#include #include #include #include -#include #include #include #include @@ -13,16 +13,14 @@ #include #include #include "mosquitto.h" +#include "apps.h" namespace trygvis { namespace mqtt_support { -template -using o = std::experimental::optional; - +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 mqtt_tokenize_topic(string path) { + char **topics; + int topic_count; + int i; + + mosquitto_sub_topic_tokenise(path.c_str(), &topics, &topic_count); + + vector 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 -class mqtt_client : private mqtt_lib { +class mqtt_client : public waitable, private mqtt_lib { template 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 &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 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::max(); + + if (len > int_max) { + len = static_cast(int_max); + } + + publish(mid, topic, qos, retain, static_cast(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 +#include +#include +#include +#include +#include +#include +#include + +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 loop{true}; + +mutex main_mutex; +condition_variable main_cv; +std::list> to_device; +std::list> to_diller; + +class diller_mqtt_client : public mqtt_client { +public: + diller_mqtt_client(const Mac mac, const string &host, const int port, const int keep_alive, + const o &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 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(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(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 lock(main_mutex); + uint8_t value = static_cast(message->payload)[0]; + + const int light = 0; + + LOG4CPLUS_INFO(logger, + "Pushing command set_light(light=" << light << ", value=" << static_cast(value) << ")"); + + to_device.push_back(make_unique(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(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 sleep_time; + vector sensorIndexes; + vector> sensors; + + void add_options(po::options_description_easy_init &options) override { + auto opt_server = po::value(&opts.server)->required(); + auto opt_client_id = po::value(&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()->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(); + + 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 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(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 lock(main_mutex); + to_diller.push_back(make_unique(sensor, value)); + } + } + + main_cv.notify_all(); + } + + void withConnection(const Logger &logger, shared_ptr 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 lock(main_mutex); + to_diller.push_back(make_unique(i, name)); + }); + + main_cv.notify_all(); + } + + auto mac = gatt->getDevice().getMac().str(); + if (!loop) { + read_sensors(soilMoisture, mac); + } else { + std::chrono::time_point target_time; + target_time = system_clock::now(); + + do { + target_time = target_time + seconds(sleep_time); + + read_sensors(soilMoisture, mac); + + do { + unique_lock 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(command); + LOG4CPLUS_DEBUG(logger, "performing Set light: light=" << static_cast(sl.light) + << ", value=" << static_cast(sl.value)); + + try { + soilMoisture.setLight(sl.light, sl.value); + } catch (std::runtime_error &e) { + LOG4CPLUS_WARN(logger, + "Could not execute setLight, light=" << static_cast(sl.light) + << ", value=" << static_cast(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> 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; -- cgit v1.2.3