diff options
author | Trygve Laugstøl <trygvis@inamo.no> | 2016-04-12 20:06:47 +0200 |
---|---|---|
committer | Trygve Laugstøl <trygvis@inamo.no> | 2016-04-12 20:06:47 +0200 |
commit | 2ca532122d60cff4dbdc7f24fbc5783bcc5ad68d (patch) | |
tree | 19f9ce796886e216a608fa5e938bd2bd4f0d0b55 /apps/sm-diller.cpp | |
parent | ce07550c57172443c10a66957b50085e273d20b3 (diff) | |
download | ble-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/sm-diller.cpp')
-rw-r--r-- | apps/sm-diller.cpp | 423 |
1 files changed, 423 insertions, 0 deletions
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); +} |