aboutsummaryrefslogtreecommitdiff
path: root/apps/sm-diller.cpp
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/sm-diller.cpp
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/sm-diller.cpp')
-rw-r--r--apps/sm-diller.cpp423
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);
+}