incremental
diff --git a/CMakeLists.txt b/CMakeLists.txt
index db4df43..1c4435e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,8 @@
 
 project(bmc-webserver CXX C)
 
+include( CTest )
+
 set(CMAKE_CXX_STANDARD 14)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
@@ -184,8 +186,6 @@
 
 
 set(SRC_FILES
-    src/token_authorization_middleware.cpp
-    src/security_headers_middleware.cpp
     src/base64.cpp
     ${GENERATED_SRC_FILES}
 )
diff --git a/crow/include/crow/http_response.h b/crow/include/crow/http_response.h
index c81446f..07493bc 100644
--- a/crow/include/crow/http_response.h
+++ b/crow/include/crow/http_response.h
@@ -47,18 +47,18 @@
   }
 
   response(response&& r) {
-    CROW_LOG_WARNING << "Moving response containers";
+    CROW_LOG_DEBUG << "Moving response containers";
     *this = std::move(r);
   }
 
   ~response(){
-    CROW_LOG_WARNING << "Destroying response";
+    CROW_LOG_DEBUG << "Destroying response";
   }
 
   response& operator=(const response& r) = delete;
 
   response& operator=(response&& r) noexcept {
-    CROW_LOG_WARNING << "Moving response containers";
+    CROW_LOG_DEBUG << "Moving response containers";
     body = std::move(r.body);
     json_value = std::move(r.json_value);
     code = r.code;
@@ -70,7 +70,7 @@
   bool is_completed() const noexcept { return completed_; }
 
   void clear() {
-    CROW_LOG_WARNING << "Clearing response containers";
+    CROW_LOG_DEBUG << "Clearing response containers";
     body.clear();
     json_value.clear();
     code = 200;
diff --git a/crow/include/crow/http_server.h b/crow/include/crow/http_server.h
index 2ead557..5ed2927 100644
--- a/crow/include/crow/http_server.h
+++ b/crow/include/crow/http_server.h
@@ -113,7 +113,6 @@
           timer.async_wait(handler);
         };
         timer.async_wait(handler);
-        CROW_LOG_INFO << init_count;
         init_count++;
         try {
           io_service_pool_[i]->run();
diff --git a/crow/include/crow/websocket.h b/crow/include/crow/websocket.h
index f29f13b..65a5836 100644
--- a/crow/include/crow/websocket.h
+++ b/crow/include/crow/websocket.h
@@ -18,6 +18,7 @@
   virtual void send_binary(const std::string& msg) = 0;
   virtual void send_text(const std::string& msg) = 0;
   virtual void close(const std::string& msg = "quit") = 0;
+  virtual boost::asio::io_service& get_io_service() = 0;
   virtual ~connection() {}
 
   void userdata(void* u) { userdata_ = u; }
@@ -70,6 +71,10 @@
     adaptor_.get_io_service().post(handler);
   }
 
+  boost::asio::io_service& get_io_service(){
+    return adaptor_.get_io_service();
+  }
+
   void send_pong(const std::string& msg) {
     dispatch([this, msg] {
       char buf[3] = "\x8A\x00";
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
new file mode 100644
index 0000000..153dbc7
--- /dev/null
+++ b/include/pam_authenticate.hpp
@@ -0,0 +1,65 @@
+#include <security/pam_appl.h>
+
+// function used to get user input
+inline int pam_function_conversation(int num_msg,
+                                     const struct pam_message** msg,
+                                     struct pam_response** resp,
+                                     void* appdata_ptr) {
+  char* pass = (char*)malloc(strlen((char*)appdata_ptr) + 1);
+  strcpy(pass, (char*)appdata_ptr);
+
+  int i;
+
+  *resp = (pam_response*)calloc(num_msg, sizeof(struct pam_response));
+
+  for (i = 0; i < num_msg; ++i) {
+    /* Ignore all PAM messages except prompting for hidden input */
+    if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF) continue;
+
+    /* Assume PAM is only prompting for the password as hidden input */
+    resp[i]->resp = pass;
+  }
+
+  return PAM_SUCCESS;
+}
+
+class PamAuthenticator {
+ public:
+  inline bool authenticate(const std::string& username,
+                           const std::string& password) {
+    const struct pam_conv local_conversation = {pam_function_conversation,
+                                                (char*)password.c_str()};
+    pam_handle_t* local_auth_handle = NULL;  // this gets set by pam_start
+
+    int retval;
+    retval = pam_start("su", username.c_str(), &local_conversation,
+                       &local_auth_handle);
+
+    if (retval != PAM_SUCCESS) {
+      //printf("pam_start returned: %d\n ", retval);
+      return false;
+    }
+
+    retval = pam_authenticate(local_auth_handle,
+                              PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
+
+    if (retval != PAM_SUCCESS) {
+      if (retval == PAM_AUTH_ERR) {
+        //printf("Authentication failure.\n");
+      } else {
+        //printf("pam_authenticate returned %d\n", retval);
+      }
+      return false;
+    }
+
+    //printf("Authenticated.\n");
+    retval = pam_end(local_auth_handle, retval);
+
+    if (retval != PAM_SUCCESS) {
+      //printf("pam_end returned\n");
+      return false;
+    }
+
+    return true;
+  }
+};
\ No newline at end of file
diff --git a/include/security_headers_middleware.hpp b/include/security_headers_middleware.hpp
index 7e84543..19644f4 100644
--- a/include/security_headers_middleware.hpp
+++ b/include/security_headers_middleware.hpp
@@ -4,12 +4,45 @@
 #include <crow/http_response.h>
 
 namespace crow {
+static const std::string strict_transport_security_key =
+    "Strict-Transport-Security";
+static const std::string strict_transport_security_value =
+    "max-age=31536000; includeSubdomains; preload";
+
+static const std::string ua_compatability_key = "X-UA-Compatible";
+static const std::string ua_compatability_value = "IE=11";
+
+static const std::string xframe_key = "X-Frame-Options";
+static const std::string xframe_value = "DENY";
+
+static const std::string xss_key = "X-XSS-Protection";
+static const std::string xss_value = "1; mode=block";
+
+static const std::string content_security_key = "X-Content-Security-Policy";
+static const std::string content_security_value = "default-src 'self'";
+
 
 struct SecurityHeadersMiddleware {
   struct context {};
 
-  void before_handle(crow::request& req, response& res, context& ctx);
+  void before_handle(crow::request& req,
+                                                       response& res,
+                                                       context& ctx) {}
 
-  void after_handle(request& req, response& res, context& ctx);
+  void after_handle(request& /*req*/,
+                                                      response& res,
+                                                      context& ctx) {
+    /*
+     TODO(ed) these should really check content types.  for example,
+     X-UA-Compatible header doesn't make sense when retrieving a JSON or
+     javascript file.  It doesn't hurt anything, it's just ugly.
+     */
+    res.add_header(strict_transport_security_key,
+                   strict_transport_security_value);
+    res.add_header(ua_compatability_key, ua_compatability_value);
+    res.add_header(xframe_key, xframe_value);
+    res.add_header(xss_key, xss_value);
+    res.add_header(content_security_key, content_security_value);
+  }
 };
 }
\ No newline at end of file
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 214fd93..d333e6c 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -4,25 +4,145 @@
 #include <crow/http_response.h>
 #include <boost/container/flat_set.hpp>
 
+#include <base64.hpp>
+
+#include <pam_authenticate.hpp>
+
 namespace crow {
 
 struct User {};
 
-struct TokenAuthorizationMiddleware {
+using random_bytes_engine =
+    std::independent_bits_engine<std::default_random_engine, CHAR_BIT,
+                                 unsigned char>;
+
+template <class AuthenticationFunction>
+struct TokenAuthorization {
   // TODO(ed) auth_token shouldn't really be passed to the context
   // it opens the possibility of exposure by and endpoint.
   // instead we should only pass some kind of "user" struct
   struct context {
-    //std::string auth_token;
+    // std::string auth_token;
   };
 
-  TokenAuthorizationMiddleware();
+  TokenAuthorization(){};
 
-  void before_handle(crow::request& req, response& res, context& ctx);
+  void before_handle(crow::request& req, response& res, context& ctx) {
+    auto return_unauthorized = [&req, &res]() {
+      res.code = 401;
+      res.end();
+    };
 
-  void after_handle(request& req, response& res, context& ctx);
+    auto return_bad_request = [&req, &res]() {
+      res.code = 400;
+      res.end();
+    };
+
+    auto return_internal_error = [&req, &res]() {
+      res.code = 500;
+      res.end();
+    };
+
+    if (req.url == "/" || boost::starts_with(req.url, "/static/")) {
+      // TODO this is total hackery to allow the login page to work before the
+      // user is authenticated.  Also, it will be quite slow for all pages
+      // instead
+      // of a one time hit for the whitelist entries.  Ideally, this should be
+      // done in the url router handler, with tagged routes for the whitelist
+      // entries. Another option would be to whitelist a minimal for based page
+      // that didn't
+      // load the full angular UI until after login
+      return;
+    }
+
+    if (req.url == "/login") {
+      if (req.method != HTTPMethod::POST) {
+        return_unauthorized();
+        return;
+      } else {
+        auto login_credentials = crow::json::load(req.body);
+        if (!login_credentials) {
+          return_bad_request();
+          return;
+        }
+        if (!login_credentials.has("username") ||
+            !login_credentials.has("password")) {
+          return_bad_request();
+          return;
+        }
+        auto username = login_credentials["username"].s();
+        auto password = login_credentials["password"].s();
+        auto p = AuthenticationFunction();
+        if (p.authenticate(username, password)) {
+          crow::json::wvalue x;
+
+          // TODO(ed) the RNG should be initialized at start, not every time we
+          // want a token
+          std::random_device rand;
+          random_bytes_engine rbe;
+          std::string token('a', 20);
+          // TODO(ed) for some reason clang-tidy finds a divide by zero error in
+          // cstdlibc here commented out for now.  Needs investigation
+          std::generate(std::begin(token), std::end(token), std::ref(rbe));  // NOLINT
+          std::string encoded_token;
+          base64::base64_encode(token, encoded_token);
+          // ctx.auth_token = encoded_token;
+          this->auth_token2.insert(encoded_token);
+
+          x["token"] = encoded_token;
+
+          res.write(json::dump(x));
+          res.add_header("Content-Type", "application/json");
+          res.end();
+        } else {
+          return_unauthorized();
+          return;
+        }
+      }
+
+    } else {  // Normal, non login, non static file request
+      // Check to make sure we're logged in
+      if (this->auth_token2.empty()) {
+        return_unauthorized();
+        return;
+      }
+      // Check for an authorization header, reject if not present
+      if (req.headers.count("Authorization") != 1) {
+        return_unauthorized();
+        return;
+      }
+
+      std::string auth_header = req.get_header_value("Authorization");
+      // If the user is attempting any kind of auth other than token, reject
+      if (!boost::starts_with(auth_header, "Token ")) {
+        return_unauthorized();
+        return;
+      }
+      std::string auth_key = auth_header.substr(6);
+      // TODO(ed), use span here instead of constructing a new string
+      if (this->auth_token2.find(auth_key) == this->auth_token2.end()) {
+        return_unauthorized();
+        return;
+      }
+
+      if (req.url == "/logout") {
+        this->auth_token2.erase(auth_key);
+        res.code = 200;
+        res.end();
+        return;
+      }
+
+      // else let the request continue unharmed
+    }
+  }
+
+  void after_handle(request& req, response& res, context& ctx) {
+    // Do nothing
+  }
 
  private:
   boost::container::flat_set<std::string> auth_token2;
 };
+
+using TokenAuthorizationMiddleware = TokenAuthorization<PamAuthenticator>;
 }
\ No newline at end of file
diff --git a/src/ast_video_puller_test.cpp b/src/ast_video_puller_test.cpp
index ef1cbe3..b1f94e7 100644
--- a/src/ast_video_puller_test.cpp
+++ b/src/ast_video_puller_test.cpp
@@ -14,7 +14,6 @@
 #include <gtest/gtest.h>
 
 TEST(AstvideoPuller, BasicRead) {
-  std::cout << "Started\n";
   AstVideo::RawVideoBuffer out;
   bool have_hardware = false;
   if (access("/dev/video", F_OK) != -1) {
@@ -43,7 +42,6 @@
   fwrite(out.buffer.data(), sizeof(char), out.buffer.size(), fp);
 
   AstVideo::AstJpegDecoder d;
-  std::cout << "MODE " << static_cast<int>(out.mode);
   d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
            out.uv_selector);
 }
diff --git a/src/getvideo_main.cpp b/src/getvideo_main.cpp
index 33885ee..2ab0c0e 100644
--- a/src/getvideo_main.cpp
+++ b/src/getvideo_main.cpp
@@ -20,7 +20,6 @@
 #include <ast_video_puller.hpp>
 
 int main() {
-  std::cout << "Started\n";
   AstVideo::RawVideoBuffer out;
   bool have_hardware = false;
   if (access("/dev/video", F_OK) != -1) {
@@ -49,7 +48,6 @@
   fwrite(out.buffer.data(), sizeof(char), out.buffer.size(), fp);
 
   AstVideo::AstJpegDecoder d;
-  std::cout << "MODE " << static_cast<int>(out.mode);
   d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
            out.uv_selector);
 #ifdef BUILD_CIMG
diff --git a/src/security_headers_middleware.cpp b/src/security_headers_middleware.cpp
deleted file mode 100644
index 265cda7..0000000
--- a/src/security_headers_middleware.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#include <security_headers_middleware.hpp>
-
-namespace crow {
-
-static const std::string strict_transport_security_key =
-    "Strict-Transport-Security";
-static const std::string strict_transport_security_value =
-    "max-age=31536000; includeSubdomains; preload";
-
-static const std::string ua_compatability_key = "X-UA-Compatible";
-static const std::string ua_compatability_value = "IE=11";
-
-static const std::string xframe_key = "X-Frame-Options";
-static const std::string xframe_value = "DENY";
-
-static const std::string xss_key = "X-XSS-Protection";
-static const std::string xss_value = "1; mode=block";
-
-static const std::string content_security_key = "X-Content-Security-Policy";
-static const std::string content_security_value = "default-src 'self'";
-
-void SecurityHeadersMiddleware::before_handle(crow::request& req, response& res,
-                                              context& ctx) {}
-
-void SecurityHeadersMiddleware::after_handle(request& /*req*/, response& res,
-                                             context& ctx) {
-  /*
-   TODO(ed) these should really check content types.  for example,
-   X-UA-Compatible header doesn't make sense when retrieving a JSON or
-   javascript file.  It doesn't hurt anything, it's just ugly.
-   */
-  res.add_header(strict_transport_security_key,
-                 strict_transport_security_value);
-  res.add_header(ua_compatability_key, ua_compatability_value);
-  res.add_header(xframe_key, xframe_value);
-  res.add_header(xss_key, xss_value);
-  res.add_header(content_security_key, content_security_value);
-}
-}
diff --git a/src/token_authorization_middleware.cpp b/src/token_authorization_middleware.cpp
deleted file mode 100644
index 508bfd9..0000000
--- a/src/token_authorization_middleware.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-#include <random>
-#include <unordered_map>
-#include <boost/algorithm/string/predicate.hpp>
-
-#include <security/pam_appl.h>
-#include <base64.hpp>
-#include <token_authorization_middleware.hpp>
-#include <crow/logging.h>
-
-namespace crow {
-
-using random_bytes_engine =
-    std::independent_bits_engine<std::default_random_engine, CHAR_BIT,
-                                 unsigned char>;
-
-// function used to get user input
-int pam_function_conversation(int num_msg, const struct pam_message** msg,
-                              struct pam_response** resp, void* appdata_ptr) {
-  char* pass = (char*)malloc(strlen((char*)appdata_ptr) + 1);
-  strcpy(pass, (char*)appdata_ptr);
-
-  int i;
-
-  *resp = (pam_response*)calloc(num_msg, sizeof(struct pam_response));
-
-  for (i = 0; i < num_msg; ++i) {
-    /* Ignore all PAM messages except prompting for hidden input */
-    if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF) continue;
-
-    /* Assume PAM is only prompting for the password as hidden input */
-    resp[i]->resp = pass;
-  }
-
-  return PAM_SUCCESS;
-}
-
-bool authenticate_user_pam(const std::string& username,
-                           const std::string& password) {
-  const struct pam_conv local_conversation = {pam_function_conversation,
-                                              (char*)password.c_str()};
-  pam_handle_t* local_auth_handle = NULL;  // this gets set by pam_start
-
-  int retval;
-  retval = pam_start("su", username.c_str(), &local_conversation,
-                     &local_auth_handle);
-
-  if (retval != PAM_SUCCESS) {
-    printf("pam_start returned: %d\n ", retval);
-    return false;
-  }
-
-  retval = pam_authenticate(local_auth_handle,
-                            PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
-
-  if (retval != PAM_SUCCESS) {
-    if (retval == PAM_AUTH_ERR) {
-      printf("Authentication failure.\n");
-    } else {
-      printf("pam_authenticate returned %d\n", retval);
-    }
-    return false;
-  }
-
-  printf("Authenticated.\n");
-  retval = pam_end(local_auth_handle, retval);
-
-  if (retval != PAM_SUCCESS) {
-    printf("pam_end returned\n");
-    return false;
-  }
-
-  return true;
-}
-
-TokenAuthorizationMiddleware::TokenAuthorizationMiddleware(){
-};
-
-void TokenAuthorizationMiddleware::before_handle(crow::request& req,
-                                                 response& res, context& ctx) {
-  auto return_unauthorized = [&req, &res]() {
-    res.code = 401;
-    res.end();
-  };
-
-  auto return_bad_request = [&req, &res]() {
-    res.code = 400;
-    res.end();
-  };
-
-  auto return_internal_error = [&req, &res]() {
-    res.code = 500;
-    res.end();
-  };
-
-  if (req.url == "/" || boost::starts_with(req.url, "/static/")) {
-    // TODO this is total hackery to allow the login page to work before the
-    // user is authenticated.  Also, it will be quite slow for all pages instead
-    // of a one time hit for the whitelist entries.  Ideally, this should be
-    // done in the url router handler, with tagged routes for the whitelist
-    // entries. Another option would be to whitelist a minimal for based page
-    // that didn't
-    // load the full angular UI until after login
-    return;
-  }
-
-  if (req.url == "/login") {
-    if (req.method != HTTPMethod::POST) {
-      return_unauthorized();
-      return;
-    } else {
-      auto login_credentials = crow::json::load(req.body);
-      if (!login_credentials) {
-        return_bad_request();
-        return;
-      }
-      if (!login_credentials.has("username") ||
-          !login_credentials.has("password")) {
-        return_bad_request();
-        return;
-      }
-      auto username = login_credentials["username"].s();
-      auto password = login_credentials["password"].s();
-
-      // TODO(ed) pull real passwords from PAM
-      if (authenticate_user_pam(username, password)) {
-        crow::json::wvalue x;
-
-        // TODO(ed) the RNG should be initialized at start, not every time we
-        // want a token
-        std::random_device rand;
-        random_bytes_engine rbe;
-        std::string token('a', 20);
-        // TODO(ed) for some reason clang-tidy finds a divide by zero error in
-        // cstdlibc here commented out for now.  Needs investigation
-        std::generate(begin(token), end(token), std::ref(rbe));  // NOLINT
-        std::string encoded_token;
-        base64::base64_encode(token, encoded_token);
-        // ctx.auth_token = encoded_token;
-        this->auth_token2.insert(encoded_token);
-
-        x["token"] = encoded_token;
-
-        res.write(json::dump(x));
-        res.add_header("Content-Type", "application/json");
-        res.end();
-      } else {
-        return_unauthorized();
-        return;
-      }
-    }
-
-  } else {  // Normal, non login, non static file request
-    // Check to make sure we're logged in
-    if (this->auth_token2.empty()) {
-      return_unauthorized();
-      return;
-    }
-    // Check for an authorization header, reject if not present
-    if (req.headers.count("Authorization") != 1) {
-      return_unauthorized();
-      return;
-    }
-
-    std::string auth_header = req.get_header_value("Authorization");
-    // If the user is attempting any kind of auth other than token, reject
-    if (!boost::starts_with(auth_header, "Token ")) {
-      return_unauthorized();
-      return;
-    }
-    std::string auth_key = auth_header.substr(6);
-    // TODO(ed), use span here instead of constructing a new string
-    if (this->auth_token2.find(auth_key) == this->auth_token2.end()) {
-      return_unauthorized();
-      return;
-    }
-
-    if (req.url == "/logout") {
-      this->auth_token2.erase(auth_key);
-      res.code = 200;
-      res.end();
-      return;
-    }
-
-    // else let the request continue unharmed
-  }
-}
-
-void TokenAuthorizationMiddleware::after_handle(request& req, response& res,
-                                                context& ctx) {
-  // Do nothing
-}
-}
diff --git a/src/token_authorization_middleware_test.cpp b/src/token_authorization_middleware_test.cpp
index e8277653..49933c9 100644
--- a/src/token_authorization_middleware_test.cpp
+++ b/src/token_authorization_middleware_test.cpp
@@ -6,6 +6,14 @@
 using namespace crow;
 using namespace std;
 
+class KnownLoginAuthenticator {
+ public:
+  inline bool authenticate(const std::string& username,
+                           const std::string& password) {
+    return (username == "dude") && (password == "foo");
+  }
+};
+
 // Tests that static urls are correctly passed
 TEST(TokenAuthentication, TestBasicReject) {
   App<crow::TokenAuthorizationMiddleware> app;
@@ -177,9 +185,8 @@
   app.stop();
 }
 
-// Tests boundary conditions on login
 TEST(TokenAuthentication, TestSuccessfulLogin) {
-  App<crow::TokenAuthorizationMiddleware> app;
+  App<crow::TokenAuthorization<KnownLoginAuthenticator>> app;
   app.bindaddr("127.0.0.1").port(45451);
   CROW_ROUTE(app, "/")([]() { return 200; });
   auto _ = async(launch::async, [&] { app.run(); });
@@ -215,7 +222,7 @@
   // Test correct login credentials
   sendmsg =
       "POST /login\r\nContent-Length:40\r\n\r\n{\"username\": \"dude\", "
-      "\"password\": \"dude\"}\r\n";
+      "\"password\": \"foo\"}\r\n";
   {
     send_to_localhost(sendmsg);
     std::string response(std::begin(buf), std::end(buf));
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index 0c173dd..3baf388 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -246,9 +246,20 @@
     return j;
   });
 
-  CROW_ROUTE(app, "/ipmiws")
+  CROW_ROUTE(app, "/sensorws")
       .websocket()
       .onopen([&](crow::websocket::connection& conn) {
+        dbus::connection system_bus(conn.get_io_service(), dbus::bus::system);
+        dbus::match ma(system_bus,
+                       "type='signal',sender='org.freedesktop.DBus', "
+                       "interface='org.freedesktop.DBus.Properties',member="
+                       "'PropertiesChanged'");
+        dbus::filter f(system_bus, [](dbus::message& m) { return true; });
+
+        f.async_dispatch([&](boost::system::error_code ec, dbus::message s) {
+          std::cout << "got event\n";
+          //f.async_dispatch(event_handler);
+        });
 
       })
       .onclose(
@@ -257,57 +268,43 @@
           })
       .onmessage([&](crow::websocket::connection& conn, const std::string& data,
                      bool is_binary) {
-        boost::asio::io_service io_service;
-        using boost::asio::ip::udp;
-        udp::resolver resolver(io_service);
-        udp::resolver::query query(udp::v4(), "10.243.48.31", "623");
-        udp::endpoint receiver_endpoint = *resolver.resolve(query);
-
-        udp::socket socket(io_service);
-        socket.open(udp::v4());
-
-        socket.send_to(boost::asio::buffer(data), receiver_endpoint);
-
-        std::array<char, 255> recv_buf;
-
-        udp::endpoint sender_endpoint;
-        size_t len =
-            socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint);
-        // TODO(ed) THis is ugly.  Find a way to not make a copy (ie, use
-        // std::string::data() to
-        std::string str(std::begin(recv_buf), std::end(recv_buf));
-        LOG(DEBUG) << "Got " << str << "back \n";
-        conn.send_binary(str);
 
       });
 
   CROW_ROUTE(app, "/sensortest")
   ([](const crow::request& req, crow::response& res) {
+    crow::json::wvalue j;
+    auto values = read_sensor_values();
+
     dbus::connection system_bus(*req.io_service, dbus::bus::system);
+    dbus::endpoint test_daemon("org.openbmc.Sensors",
+                               "/org/openbmc/sensors/tach",
+                               "org.freedesktop.DBus.Introspectable");
+    dbus::message m = dbus::message::new_call(test_daemon, "Introspect");
+    system_bus.async_send(
+        m,
+        [&j, &system_bus](const boost::system::error_code ec, dbus::message r) {
+          std::string xml;
+          r.unpack(xml);
+          std::vector<std::string> dbus_objects;
+          dbus::read_dbus_xml_names(xml, dbus_objects);
 
-    dbus::endpoint test_daemon("org.freedesktop.DBus", "/",
-                               "org.freedesktop.DBus");
-    dbus::message m = dbus::message::new_call(test_daemon, "ListNames");
-    system_bus.async_send(m, [&](const boost::system::error_code ec,
-                                 dbus::message r) {
-      std::vector<std::string> services;
-      //r.unpack(services);
-      for (auto& service : services) {
-        dbus::endpoint service_daemon(service, "/",
-                                      "org.freedesktop.DBus.Introspectable");
-        dbus::message m = dbus::message::new_call(service_daemon, "Introspect");
-        system_bus.async_send(
-            m, [&](const boost::system::error_code ec, dbus::message r) {
-              std::string xml;
-              r.unpack(xml);
-              std::vector<std::string> dbus_objects;
-              dbus::read_dbus_xml_names(xml, dbus_objects);
+          for (auto& object : dbus_objects) {
+            dbus::endpoint test_daemon("org.openbmc.Sensors",
+                                       "/org/openbmc/sensors/tach/" + object,
+                                       "org.openbmc.SensorValue");
+            dbus::message m2 = dbus::message::new_call(test_daemon, "getValue");
+            
+            system_bus.async_send(
+                m2, [&](const boost::system::error_code ec, dbus::message r) {
+                  int32_t value;
+                  r.unpack(value);
+                  // TODO(ed) if we ever go multithread, j needs a lock
+                  j[object] = value;
+                });
+          }
 
-
-            });
-      }
-
-    });
+        });
 
   });
 
@@ -337,6 +334,6 @@
     auto ssl_context = ensuressl::get_ssl_context(ssl_pem_file);
     app.ssl(std::move(ssl_context));
   }
-  app.concurrency(4);
+  //app.concurrency(4);
   app.run();
 }