From 4671571c93db7f083db21cfca526ac382cdfc7ad Mon Sep 17 00:00:00 2001
From: Trygve Laugstøl <trygvis@inamo.no>
Date: Mon, 23 Feb 2015 23:04:38 +0100
Subject: Major improvement in the sm-get-value utility: o Adding command line
 option parsing through boost::program_option   Current options:   --device,
 --sensor   --format selects between CSV and JSON output.   --sleep controls
 single reads vs continuous reads. o Adding JSON output. o Adding support for
 Release builds without debugging info. Needs improvement.

---
 apps/CMakeLists.txt   |  16 +++++-
 apps/sm-get-value.cpp | 139 +++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 141 insertions(+), 14 deletions(-)

(limited to 'apps')

diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index e210716..9d68f79 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -2,7 +2,7 @@ set(APPS sm-get-value ble-inspect-device)
 set(shared_sources SoilMoisture.cpp)
 
 # Boost
-find_package(Boost COMPONENTS system log thread REQUIRED)
+find_package(Boost COMPONENTS system log thread program_options REQUIRED)
 
 # Bluez
 pkg_check_modules(BLUEZ bluez REQUIRED)
@@ -10,8 +10,21 @@ pkg_check_modules(BLUEZ bluez REQUIRED)
 # pthreads
 find_package(Threads REQUIRED)
 
+pkg_check_modules(PQXX libpqxx REQUIRED)
+
+include(ExternalProject)
+ExternalProject_Add(
+    JSON
+    PREFIX json
+    GIT_REPOSITORY https://github.com/nlohmann/json.git
+    GIT_TAG ec42245951fceb7594bfb24746c7449986c3c2a4
+    CONFIGURE_COMMAND ""
+    BUILD_COMMAND ""
+    INSTALL_COMMAND "")
+
 foreach(app ${APPS})
     include_directories("${PROJECT_SOURCE_DIR}/include")
+    include_directories("${CMAKE_BINARY_DIR}/apps/json/src/JSON/src")
 
     add_executable(${app} ${app}.cpp ${shared_sources})
     add_dependencies(${app} ble)
@@ -19,5 +32,6 @@ foreach(app ${APPS})
     target_link_libraries(${app} ble)
     target_link_libraries(${app} ${Boost_LIBRARIES})
     target_link_libraries(${app} ${BLUEZ_LIBRARIES})
+    target_link_libraries(${app} ${PQXX_LIBRARIES})
     target_link_libraries(${app} ${CMAKE_THREAD_LIBS_INIT})
 endforeach(app)
diff --git a/apps/sm-get-value.cpp b/apps/sm-get-value.cpp
index 46177f2..97858f4 100644
--- a/apps/sm-get-value.cpp
+++ b/apps/sm-get-value.cpp
@@ -1,34 +1,137 @@
 #include <iostream>
+#include <iomanip>
+#include <chrono>
 #include <boost/uuid/uuid_io.hpp>
 #include <boost/optional.hpp>
+#include <boost/program_options.hpp>
+#include <thread>
 #include "ble/Bluetooth.h"
 #include "SoilMoisture.h"
+#include "json.hpp"
 
+// I'm lazy
 using namespace std;
+using namespace std::chrono;
 using namespace trygvis::bluetooth;
 using namespace trygvis::soil_moisture;
+using json = nlohmann::json;
 
-bool loop = true;
+enum class Format {
+    PLAIN,
+    JSON
+};
+
+void validate(boost::any &v, const std::vector<std::string> &values, Format *, int) {
+    using namespace boost::program_options;
+
+    const std::string &s = validators::get_single_string(values);
+
+    if (s == "json") {
+        v = boost::any(Format::JSON);
+    } else if (s == "plain") {
+        v = boost::any(Format::PLAIN);
+    } else {
+        throw validation_error(validation_error::invalid_option_value);
+    }
+}
+
+namespace boost {
+
+template<>
+string lexical_cast(const Format &arg) {
+    if (arg == Format::PLAIN)
+        return "plain";
+    else if (arg == Format::JSON)
+        return "json";
+    else
+        throw std::runtime_error("Unknown Format value: " + to_string(static_cast<std::underlying_type<Format>::type>(arg)));
+}
+
+}
+
+bool loop;
+Format format;
+time_point<system_clock> targetTime;
+unsigned int sleepTime;
+vector<unsigned int> sensors;
 
 void withConnection(BluetoothGatt &gatt) {
     SoilMoisture soilMoisture = SoilMoisture::create(gatt);
 
-    int sensorCount = soilMoisture.getSensorCount();
+    const int sensorCount = soilMoisture.getSensorCount();
 
-    while (loop) {
-        for (uint8_t i = 0; i < sensorCount; i++) {
-            uint16_t value = soilMoisture.getValue(i);
+    if (sensorCount == 0) {
+        throw runtime_error("Sensor count is 0");
+    }
 
-            cout << "sensor=" << to_string(i) << ", value=" << (int) value << endl;
+    // If the user didn't specify any sensors, add all.
+    if (sensors.size() == 0) {
+        for (int i = 0; i < sensorCount; i++) {
+            sensors.push_back(i);
         }
-
-        sleep(1);
     }
+
+    auto device = gatt.getDevice().getMac();
+
+    targetTime = system_clock::now();
+
+    do {
+        for (auto sensor : sensors) {
+            if (sensor >= sensorCount) {
+                // Ignore invalid sensors
+                continue;
+            }
+
+            auto epoch = system_clock::now().time_since_epoch();
+            auto timestamp = duration_cast<seconds>(epoch).count();
+            uint16_t value = soilMoisture.getValue((uint8_t) sensor);
+
+            if (format == Format::PLAIN) {
+                cout << "device=" << device.str()
+                        << ", sensor=" << to_string(sensor)
+                        << ", timestamp=" << to_string(timestamp)
+                        << ", value=" << (int) value << endl;
+            } else if (format == Format::JSON) {
+                json j;
+                j["device"] = device.str();
+                j["sensor"] = sensor;
+                j["timestamp"] = timestamp;
+                j["value"] = value;
+                cout << j << endl;
+            }
+        }
+
+        targetTime = targetTime + seconds(sleepTime);
+        this_thread::sleep_until(targetTime);
+    } while (loop);
 }
 
 int main(int argc, char *argv[]) {
-    if (argc != 2) {
-        cerr << "usage: " << argv[0] << " [mac]" << endl;
+    namespace po = boost::program_options;
+
+    po::options_description desc("Options");
+    desc.add_options()
+            ("help", "produce help message")
+            ("device", po::value<string>()->required(), "MAC of device to poll")
+            ("sensor", po::value<vector<unsigned int>>(&sensors)->multitoken(), "Sensor to poll, defaults to all")
+            ("sleep", po::value<unsigned int>(&sleepTime)->default_value(0),
+                    "How long to sleep in seconds between each poll. If not give, it will exit after first poll")
+            ("format", po::value<Format>(&format)->default_value(Format::PLAIN), "Output format");
+
+    po::variables_map vm;
+    auto parsed = po::parse_command_line(argc, argv, desc);
+    po::store(parsed, vm);
+    po::notify(vm);
+
+    auto unrecognized = po::collect_unrecognized(parsed.options, po::include_positional);
+
+    if (vm.count("help")) {
+        cerr << desc << "\n";
+        return EXIT_FAILURE;
+    }
+
+    if (unrecognized.size()) {
+        cerr << "Unrecognized option: " << unrecognized.at(0) << "\n";
         return EXIT_FAILURE;
     }
 
@@ -36,13 +139,23 @@ int main(int argc, char *argv[]) {
     BluetoothSystem bluetoothSystem;
 
     try {
-        Mac mac = Mac::parseMac(argv[1]);
+        if (!vm.count("device")) {
+            cerr << "Missing required option: device" << endl;
+            cerr << desc << "\n";
+            return EXIT_FAILURE;
+        }
+
+        auto MAC = vm["device"].as<string>();
+        cout << "MAC " << MAC << "wat" << endl;
+        Mac mac = Mac::parseMac(MAC);
 
         auto &adapter = getAdapter(0);
 
         auto &device = adapter.getDevice(mac);
 
-        while (loop) {
+        loop = sleepTime > 0;
+
+        do {
             cout << "Connecting to device: " << device.getMac().str() << endl;
 
             auto &gatt = device.connectGatt();
@@ -52,7 +165,7 @@ int main(int argc, char *argv[]) {
                 cout << "exception: " << e.what() << endl;
             }
             gatt.disconnect();
-        }
+        } while (loop);
 
         return EXIT_SUCCESS;
     } catch (std::runtime_error ex) {
-- 
cgit v1.2.3