Merge remote-tracking branch 'weave/master' into 'weave/aosp-master'

c93071c libweave: Always fetch commands from server, even if XMPP delivers JSON
c507804 libweave: Remove backup polling (30 minute poll interval)
eae016c clean up example/test_device clashes
fcffce3 disable builtin rules
fcf6b3e add standard "check" target
825c55c readme: document cross-compiling for end users
c0c256c Add "make coverage" target to build code coverage.
e2d68d4 Add general-use-and-purpose comment for Device interface.
39b96b6 create generic test device for multiple traits testing.
2fd3e55 libuweave: Fix break on Android toolchain

Change-Id: I99d58082cd31aa997a52e0beab55a8b795581f00
diff --git a/Makefile b/Makefile
index e348fd8..0eff8e1 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,11 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+# Disable all builtin rules first as we don't use any of them (we define all
+# rules/targets ourselves, nor do we want to rely on them.
+MAKEFLAGS += --no-builtin-rules
+.SUFFIXES:
+
 # Run make with BUILD_MODE=Release for release.
 BUILD_MODE ?= Debug
 
@@ -76,7 +81,7 @@
 out/$(BUILD_MODE)/libweave.so : out/$(BUILD_MODE)/libweave_common.a
 	$(CXX) -shared -Wl,-soname=libweave.so -o $@ -Wl,--whole-archive $^ -Wl,--no-whole-archive -lcrypto -lexpat -lpthread -lrt
 
-include cross.mk file_lists.mk third_party/third_party.mk examples/examples.mk tests.mk
+include cross.mk file_lists.mk third_party/third_party.mk examples/examples.mk tests.mk tests_schema/tests_schema.mk
 
 ###
 # src/
@@ -94,7 +99,7 @@
 all-libs : out/$(BUILD_MODE)/libweave.so
 all-tests : out/$(BUILD_MODE)/libweave_exports_testrunner out/$(BUILD_MODE)/libweave_testrunner
 
-all : all-libs all-examples all-tests
+all : all-libs all-examples all-tests all-testdevices
 
 clean :
 	rm -rf out
diff --git a/README.md b/README.md
index 428b0f5..c179e61 100644
--- a/README.md
+++ b/README.md
@@ -128,6 +128,29 @@
 
 ### Cross-compiling
 
+#### libweave users
+
+In order to cross-compile, all you need to configure is CC/CXX/AR.
+
+```
+make CC=your-cc CXX=your-cxx AR=your-ar
+```
+
+So if you have a toolchain in a path like `/opt/vendor/bin/arm-linux-gcc`, do:
+
+```
+make \
+  CC=/opt/vendor/bin/arm-linux-gcc \
+  CXX=/opt/vendor/bin/arm-linux-g++ \
+  AR=/opt/vendor/bin/arm-linux-ar
+```
+
+#### libweave developers
+
+*** note
+**Note:** This is only for developers who are hacking on libweave itself.
+***
+
 The build supports transparently downloading & using a few cross-compilers.
 Just add `cross-<arch>` to the command line in addition to the target you
 want to actually build.
diff --git a/examples/examples.mk b/examples/examples.mk
index 48a1210..555322c 100644
--- a/examples/examples.mk
+++ b/examples/examples.mk
@@ -7,16 +7,6 @@
 
 examples_provider_obj_files := $(EXAMPLES_PROVIDER_SRC_FILES:%.cc=out/$(BUILD_MODE)/%.o)
 
-USE_INTERNAL_LIBEVHTP ?= 1
-
-ifeq (1, $(USE_INTERNAL_LIBEVHTP))
-LIBEVHTP_INCLUDES = -Ithird_party/libevhtp -I$(dir $(third_party_libevhtp_header))
-LIBEVHTP_HEADERS = $(third_party_libevhtp_header)
-else
-LIBEVHTP_INCLUDES =
-LIBEVHTP_HEADERS =
-endif
-
 $(examples_provider_obj_files) : $(LIBEVHTP_HEADERS)
 $(examples_provider_obj_files) : INCLUDES += $(LIBEVHTP_INCLUDES)
 $(examples_provider_obj_files) : out/$(BUILD_MODE)/%.o : %.cc
@@ -43,7 +33,7 @@
 	mkdir -p $(dir $@)
 	$(CXX) $(DEFS_$(BUILD_MODE)) $(INCLUDES) $(CFLAGS) $(CFLAGS_$(BUILD_MODE)) $(CFLAGS_CC) -c -o $@ $<
 
-daemon_common_flags := \
+example_daemon_common_flags := \
 	-Wl,-rpath=out/$(BUILD_MODE)/ \
 	-levent \
 	-levent_openssl \
@@ -55,31 +45,31 @@
 	-lssl \
 	-lcrypto
 
-daemon_deps := out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so
+example_daemon_deps := out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so
 
 ifeq (1, $(USE_INTERNAL_LIBEVHTP))
-daemon_deps += $(third_party_libevhtp_lib)
+example_daemon_deps += $(third_party_libevhtp_lib)
 else
-daemon_common_flags += -levhtp
+example_daemon_common_flags += -levhtp
 endif
 
-out/$(BUILD_MODE)/weave_daemon_ledflasher : out/$(BUILD_MODE)/examples/daemon/ledflasher/ledflasher.o $(daemon_deps)
-	$(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags)
+out/$(BUILD_MODE)/weave_daemon_ledflasher : out/$(BUILD_MODE)/examples/daemon/ledflasher/ledflasher.o $(example_daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(example_daemon_common_flags)
 
-out/$(BUILD_MODE)/weave_daemon_light : out/$(BUILD_MODE)/examples/daemon/light/light.o $(daemon_deps)
-	$(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags)
+out/$(BUILD_MODE)/weave_daemon_light : out/$(BUILD_MODE)/examples/daemon/light/light.o $(example_daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(example_daemon_common_flags)
 
-out/$(BUILD_MODE)/weave_daemon_lock : out/$(BUILD_MODE)/examples/daemon/lock/lock.o $(daemon_deps)
-	$(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags)
+out/$(BUILD_MODE)/weave_daemon_lock : out/$(BUILD_MODE)/examples/daemon/lock/lock.o $(example_daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(example_daemon_common_flags)
 
-out/$(BUILD_MODE)/weave_daemon_oven : out/$(BUILD_MODE)/examples/daemon/oven/oven.o $(daemon_deps)
-	$(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags)
+out/$(BUILD_MODE)/weave_daemon_oven : out/$(BUILD_MODE)/examples/daemon/oven/oven.o $(example_daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(example_daemon_common_flags)
 
-out/$(BUILD_MODE)/weave_daemon_sample : out/$(BUILD_MODE)/examples/daemon/sample/sample.o $(daemon_deps)
-	$(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags)
+out/$(BUILD_MODE)/weave_daemon_sample : out/$(BUILD_MODE)/examples/daemon/sample/sample.o $(example_daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(example_daemon_common_flags)
 
-out/$(BUILD_MODE)/weave_daemon_speaker : out/$(BUILD_MODE)/examples/daemon/speaker/speaker.o $(daemon_deps)
-	$(CXX) -o $@ $^ $(CFLAGS) $(daemon_common_flags)
+out/$(BUILD_MODE)/weave_daemon_speaker : out/$(BUILD_MODE)/examples/daemon/speaker/speaker.o $(example_daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(example_daemon_common_flags)
 
 all-examples : out/$(BUILD_MODE)/weave_daemon_ledflasher out/$(BUILD_MODE)/weave_daemon_light out/$(BUILD_MODE)/weave_daemon_lock out/$(BUILD_MODE)/weave_daemon_oven out/$(BUILD_MODE)/weave_daemon_sample out/$(BUILD_MODE)/weave_daemon_speaker
 
diff --git a/include/weave/device.h b/include/weave/device.h
index b79e6a3..5d455de 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -23,6 +23,25 @@
 
 namespace weave {
 
+// This interface defines interactions with a Weave device.  Implemented in
+// weave::DeviceManager.  The general use of this class is to:
+// - Define the traits for the Weave schema the device is
+//    interested in using the AddTraitDefinitions() and
+//    AddTraitDefinitionsFromJson() functions.
+// - Define the components for the Weave schema the device is
+//    interested in using the AddComponent() function.
+// - Set the initial/default values of "state" portions of the schema using
+//    the SetStatePropertiesFromJson() and SetStateProperties() functions.
+// - Specify callback functions when a command (defined in the Weave schema)
+//    is received by the device using the AddCommandHandler() function.
+//
+// A daemon will typically create one instance of a class implementing
+// Device and will retain it for the life of the daemon using the Create()
+// function.
+//
+// For detailed examples of the usage of this class, see the examples/daemon
+// directory.
+
 // States of Gcd connection.
 enum class GcdState {
   kUnconfigured,        // Device was not registered.
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index a278e63..195895f 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -46,7 +46,6 @@
 const char kDeviceStart[] = "device_start";  // Initial queue fetch at startup.
 const char kRegularPull[] = "regular_pull";  // Regular fetch before XMPP is up.
 const char kNewCommand[] = "new_command";    // A new command is available.
-const char kJustInCase[] = "just_in_case";   // Backup fetch when XMPP is live.
 
 }  // namespace fetch_reason
 
@@ -452,8 +451,17 @@
   // Start with just regular polling at the pre-configured polling interval.
   // Once the primary notification channel is connected successfully, it will
   // call back to OnConnected() and at that time we'll switch to use the
-  // primary channel and switch periodic poll into much more infrequent backup
-  // poll mode.
+  // primary channel and turn off the periodic polling.
+  StartPullChannel();
+
+  notification_channel_starting_ = true;
+  primary_notification_channel_.reset(
+      new XmppChannel{GetSettings().robot_account, access_token_,
+                      GetSettings().xmpp_endpoint, task_runner_, network_});
+  primary_notification_channel_->Start(this);
+}
+
+void DeviceRegistrationInfo::StartPullChannel() {
   const base::TimeDelta pull_interval =
       base::TimeDelta::FromSeconds(kPollingPeriodSeconds);
   if (!pull_channel_) {
@@ -463,12 +471,12 @@
     pull_channel_->UpdatePullInterval(pull_interval);
   }
   current_notification_channel_ = pull_channel_.get();
+}
 
-  notification_channel_starting_ = true;
-  primary_notification_channel_.reset(
-      new XmppChannel{GetSettings().robot_account, access_token_,
-                      GetSettings().xmpp_endpoint, task_runner_, network_});
-  primary_notification_channel_->Start(this);
+void DeviceRegistrationInfo::StopPullChannel() {
+  pull_channel_->Stop();
+  pull_channel_.reset();
+  current_notification_channel_ = nullptr;
 }
 
 void DeviceRegistrationInfo::AddGcdStateChangedCallback(
@@ -1278,8 +1286,7 @@
             << channel_name;
   CHECK_EQ(primary_notification_channel_->GetName(), channel_name);
   notification_channel_starting_ = false;
-  pull_channel_->UpdatePullInterval(
-      base::TimeDelta::FromMinutes(kBackupPollingPeriodMinutes));
+  StopPullChannel();
   current_notification_channel_ = primary_notification_channel_.get();
 
   // If we have not successfully connected to the cloud server and we have not
@@ -1304,9 +1311,8 @@
   if (!HaveRegistrationCredentials() || !connected_to_cloud_)
     return;
 
-  pull_channel_->UpdatePullInterval(
-      base::TimeDelta::FromSeconds(kPollingPeriodSeconds));
-  current_notification_channel_ = pull_channel_.get();
+  // Restart polling.
+  StartPullChannel();
   UpdateDeviceResource(base::Bind(&IgnoreCloudError));
 }
 
@@ -1319,33 +1325,16 @@
 
 void DeviceRegistrationInfo::OnCommandCreated(
     const base::DictionaryValue& command,
-    const std::string& channel_name) {
+    const std::string& /* channel_name */) {
   if (!connected_to_cloud_)
     return;
 
   VLOG(1) << "Command notification received: " << command;
 
-  if (!command.empty()) {
-    // GCD spec indicates that the command parameter in notification object
-    // "may be empty if command size is too big".
-    PublishCommand(command);
-    return;
-  }
-
-  // If this request comes from a Pull channel while the primary notification
-  // channel (XMPP) is active, we are doing a backup poll, so mark the request
-  // appropriately.
-  bool just_in_case =
-      (channel_name == kPullChannelName) &&
-      (current_notification_channel_ == primary_notification_channel_.get());
-
-  std::string reason =
-      just_in_case ? fetch_reason::kJustInCase : fetch_reason::kNewCommand;
-
   // If the command was too big to be delivered over a notification channel,
   // or OnCommandCreated() was initiated from the Pull notification,
   // perform a manual command fetch from the server here.
-  FetchAndPublishCommands(reason);
+  FetchAndPublishCommands(fetch_reason::kNewCommand);
 }
 
 void DeviceRegistrationInfo::OnDeviceDeleted(const std::string& cloud_id) {
@@ -1378,10 +1367,7 @@
     primary_notification_channel_->Stop();
     primary_notification_channel_.reset();
   }
-  if (pull_channel_) {
-    pull_channel_->Stop();
-    pull_channel_.reset();
-  }
+  StopPullChannel();
   notification_channel_starting_ = false;
   SetGcdState(GcdState::kInvalidCredentials);
 }
diff --git a/src/device_registration_info.h b/src/device_registration_info.h
index db08ef9..5ecdcac 100644
--- a/src/device_registration_info.h
+++ b/src/device_registration_info.h
@@ -161,6 +161,10 @@
   // restarted anytime the access_token is refreshed.
   void StartNotificationChannel();
 
+  // Helpers to start and stop pull notification channel.
+  void StartPullChannel();
+  void StopPullChannel();
+
   // Do a HTTPS request to cloud services.
   // Handles many cases like reauthorization, 5xx HTTP response codes
   // and device removal.  It is a recommended way to do cloud API
diff --git a/tests.mk b/tests.mk
index c7db877..a02100b 100644
--- a/tests.mk
+++ b/tests.mk
@@ -64,6 +64,22 @@
 	$(TEST_ENV) $< $(TEST_FLAGS)
 
 testall : test export-test
+check : testall
 
-.PHONY : test export-test testall
+###
+# coverage
+# This runs coverage against unit tests, invoke with "make coverage".
+# Output "homepage" is out/$(BUILD_MODE)/coverage_html/index.html
+# Running a mode other than Debug will result in incorrect coverage data.
+# https://gcc.gnu.org/onlinedocs/gcc/Gcov-and-Optimization.html
 
+coverage: CFLAGS+=--coverage
+
+run_coverage: test
+	lcov --capture --directory out/$(BUILD_MODE) --output-file out/$(BUILD_MODE)/coverage.info
+	lcov -b . --remove out/$(BUILD_MODE)/coverage.info "*third_party*" "/usr/include/*" "*/include/weave/test/*" "*/src/test/*" "*/include/weave/provider/test/*" -o out/$(BUILD_MODE)/coverage_filtered.info
+	genhtml out/$(BUILD_MODE)/coverage_filtered.info --output-directory out/$(BUILD_MODE)/coverage_html
+
+coverage: run_coverage
+
+.PHONY : check coverage run_coverage test export-test testall
diff --git a/tests_schema/daemon/testdevice/standard_traits.h b/tests_schema/daemon/testdevice/standard_traits.h
new file mode 100644
index 0000000..354c783
--- /dev/null
+++ b/tests_schema/daemon/testdevice/standard_traits.h
@@ -0,0 +1,216 @@
+// Copyright 2016 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+namespace standardtraits {
+const char kTraits[] = R"({
+  "lock": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "lockedState": {
+            "type": "string",
+            "enum": [ "locked", "unlocked" ]
+          }
+        },
+        "errors": [ "jammed", "lockingNotSupported" ]
+      }
+    },
+    "state": {
+      "lockedState": {
+        "type": "string",
+        "enum": [ "locked", "unlocked", "partiallyLocked" ],
+        "isRequired": true
+      },
+      "isLockingSupported": {
+        "type": "boolean",
+        "isRequired": true
+      }
+    }
+  },
+  "onOff": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "state": {
+            "type": "string",
+            "enum": [ "on", "off" ]
+          }
+        }
+      }
+    },
+    "state": {
+      "state": {
+        "type": "string",
+        "enum": [ "on", "off" ],
+        "isRequired": true
+      }
+    }
+  },
+  "brightness": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "brightness": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      }
+    },
+    "state": {
+      "brightness": {
+        "isRequired": true,
+        "type": "number",
+        "minimum": 0.0,
+        "maximum": 1.0
+      }
+    }
+  },
+  "colorTemp": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "colorTemp": {
+            "type": "integer"
+          }
+        }
+      }
+    },
+    "state": {
+      "colorTemp": {
+        "isRequired": true,
+        "type": "integer"
+      },
+      "minColorTemp": {
+        "isRequired": true,
+        "type": "integer"
+      },
+      "maxColorTemp": {
+        "isRequired": true,
+        "type": "integer"
+      }
+    }
+  },
+  "colorXy": {
+    "commands": {
+      "setConfig": {
+        "minimalRole": "user",
+        "parameters": {
+          "colorSetting": {
+            "type": "object",
+            "required": [
+              "colorX",
+              "colorY"
+            ],
+            "properties": {
+              "colorX": {
+                "type": "number",
+                "minimum": 0.0,
+                "maximum": 1.0
+              },
+              "colorY": {
+                "type": "number",
+                "minimum": 0.0,
+                "maximum": 1.0
+              }
+            },
+            "additionalProperties": false
+          }
+        },
+        "errors": ["colorOutOfRange"]
+      }
+    },
+    "state": {
+      "colorSetting": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      },
+      "colorCapRed": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      },
+      "colorCapGreen": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      },
+      "colorCapBlue": {
+        "type": "object",
+        "isRequired": true,
+        "required": [ "colorX", "colorY" ],
+        "properties": {
+          "colorX": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          },
+          "colorY": {
+            "type": "number",
+            "minimum": 0.0,
+            "maximum": 1.0
+          }
+        }
+      }
+    }
+  }
+})";
+
+const char kDefaultState[] = R"({
+  "lock":{"isLockingSupported": true},
+  "onOff":{"state": "on"},
+  "brightness":{"brightness": 0.0},
+  "colorTemp":{"colorTemp": 0},
+    "colorXy": {
+    "colorSetting": {"colorX": 0.0, "colorY": 0.0},
+    "colorCapRed":  {"colorX": 0.674, "colorY": 0.322},
+    "colorCapGreen":{"colorX": 0.408, "colorY": 0.517},
+    "colorCapBlue": {"colorX": 0.168, "colorY": 0.041}
+  }
+})";
+
+const char kComponent[] = "testdevice";
+}  // namespace jsontraits
diff --git a/tests_schema/daemon/testdevice/testdevice.cc b/tests_schema/daemon/testdevice/testdevice.cc
new file mode 100644
index 0000000..7dea5ac
--- /dev/null
+++ b/tests_schema/daemon/testdevice/testdevice.cc
@@ -0,0 +1,277 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <string>
+#include <typeinfo>
+
+#include "examples/daemon/common/daemon.h"
+#include "tests_schema/daemon/testdevice/standard_traits.h"
+
+#include <weave/device.h>
+#include <weave/enum_to_string.h>
+
+#include <base/bind.h>
+#include <base/memory/weak_ptr.h>
+
+namespace weave {
+namespace lockstate {
+enum class LockState { kUnlocked, kLocked, kPartiallyLocked };
+
+const weave::EnumToStringMap<LockState>::Map kLockMapMethod[] = {
+    {LockState::kLocked, "locked"},
+    {LockState::kUnlocked, "unlocked"},
+    {LockState::kPartiallyLocked, "partiallyLocked"}};
+}  // namespace lockstate
+
+template <>
+EnumToStringMap<lockstate::LockState>::EnumToStringMap()
+    : EnumToStringMap(lockstate::kLockMapMethod) {}
+}  // namespace weave
+
+// TestDeviceHandler is a command handler example that shows
+// how to handle commands for a Weave testdevice.
+class TestDeviceHandler {
+ public:
+  TestDeviceHandler() = default;
+  void Register(weave::Device* device) {
+    device_ = device;
+
+    device->AddTraitDefinitionsFromJson(standardtraits::kTraits);
+    CHECK(device->AddComponent(
+        standardtraits::kComponent,
+        {"lock", "onOff", "brightness", "colorTemp", "colorXy"}, nullptr));
+    CHECK(device->SetStatePropertiesFromJson(
+        standardtraits::kComponent, standardtraits::kDefaultState, nullptr));
+
+    UpdateTestDeviceState();
+
+    device->AddCommandHandler(standardtraits::kComponent, "onOff.setConfig",
+                              base::Bind(&TestDeviceHandler::OnOnOffSetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(standardtraits::kComponent, "lock.setConfig",
+                              base::Bind(&TestDeviceHandler::OnLockSetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(
+        standardtraits::kComponent, "brightness.setConfig",
+        base::Bind(&TestDeviceHandler::OnBrightnessSetConfig,
+                   weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(
+        standardtraits::kComponent, "colorTemp.setConfig",
+        base::Bind(&TestDeviceHandler::OnColorTempSetConfig,
+                   weak_ptr_factory_.GetWeakPtr()));
+    device->AddCommandHandler(standardtraits::kComponent, "colorXy.setConfig",
+                              base::Bind(&TestDeviceHandler::OnColorXySetConfig,
+                                         weak_ptr_factory_.GetWeakPtr()));
+  }
+
+ private:
+  void OnLockSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    std::string requested_state;
+    if (params.GetString("lockedState", &requested_state)) {
+      LOG(INFO) << cmd->GetName() << " state: " << requested_state;
+
+      weave::lockstate::LockState new_lock_status;
+
+      if (!weave::StringToEnum(requested_state, &new_lock_status)) {
+        // Invalid lock state was specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (new_lock_status != lock_state_) {
+        lock_state_ = new_lock_status;
+
+        LOG(INFO) << "Lock is now: " << requested_state;
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+    AbortCommand(cmd);
+  }
+
+  void OnBrightnessSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    double brightness_value = 0.0;
+    if (params.GetDouble("brightness", &brightness_value)) {
+      LOG(INFO) << cmd->GetName() << " brightness: " << brightness_value;
+
+      if (brightness_value < 0.0 || brightness_value > 1.0) {
+        // Invalid brightness range value is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (brightness_state_ != brightness_value) {
+        brightness_state_ = brightness_value;
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+    AbortCommand(cmd);
+  }
+
+  void OnOnOffSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    std::string requested_state;
+    if (params.GetString("state", &requested_state)) {
+      LOG(INFO) << cmd->GetName() << " state: " << requested_state;
+
+      std::string temp_state = requested_state;
+      std::transform(temp_state.begin(), temp_state.end(), temp_state.begin(),
+                     ::toupper);
+      if (temp_state != "ON" && temp_state != "OFF") {
+        // Invalid OnOff state is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      bool new_light_status = requested_state == "on";
+      if (new_light_status != light_status_) {
+        light_status_ = new_light_status;
+        LOG(INFO) << "Light is now: " << (light_status_ ? "ON" : "OFF");
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+    AbortCommand(cmd);
+  }
+
+  void OnColorTempSetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+
+    const auto& params = cmd->GetParameters();
+    int32_t color_temp = 0;
+    if (params.GetInteger("colorTemp", &color_temp)) {
+      LOG(INFO) << cmd->GetName() << " colorTemp: " << color_temp;
+
+      if (color_temp < 0.0 || color_temp > 1.0) {
+        // Invalid color_temp value is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (color_temp != color_temp_) {
+        color_temp_ = color_temp;
+
+        LOG(INFO) << "color_Temp is now: " << color_temp_;
+        UpdateTestDeviceState();
+      }
+      cmd->Complete({}, nullptr);
+      return;
+    }
+
+    AbortCommand(cmd);
+  }
+
+  void OnColorXySetConfig(const std::weak_ptr<weave::Command>& command) {
+    auto cmd = command.lock();
+    if (!cmd)
+      return;
+    LOG(INFO) << "received command: " << cmd->GetName();
+    const auto& params = cmd->GetParameters();
+    const base::DictionaryValue* colorXy = nullptr;
+    if (params.GetDictionary("colorSetting", &colorXy)) {
+      bool updateState = false;
+      double X = 0.0;
+      double Y = 0.0;
+      if (colorXy->GetDouble("colorX", &X)) {
+        color_X_ = X;
+        updateState = true;
+      }
+
+      if (colorXy->GetDouble("colorY", &Y)) {
+        color_Y_ = Y;
+        updateState = true;
+      }
+
+      if ((color_X_ < 0.0 || color_Y_ > 1.0) ||
+          (color_Y_ < 0.0 || color_Y_ > 1.0)) {
+        // Invalid color range value is specified.
+        AbortCommand(cmd);
+        return;
+      }
+
+      if (updateState)
+        UpdateTestDeviceState();
+
+      cmd->Complete({}, nullptr);
+      return;
+    }
+
+    AbortCommand(cmd);
+  }
+
+  void UpdateTestDeviceState() {
+    std::string updated_state = weave::EnumToString(lock_state_);
+    device_->SetStateProperty(standardtraits::kComponent, "lock.lockedState",
+                              base::StringValue{updated_state}, nullptr);
+    base::DictionaryValue state;
+    state.SetString("onOff.state", light_status_ ? "on" : "off");
+    state.SetDouble("brightness.brightness", brightness_state_);
+    state.SetInteger("colorTemp.minColorTemp", color_temp_min_value_);
+    state.SetInteger("colorTemp.maxColorTemp", color_temp_max_value_);
+    state.SetInteger("colorTemp.colorTemp", color_temp_);
+
+    std::unique_ptr<base::DictionaryValue> colorXy(new base::DictionaryValue());
+    colorXy->SetDouble("colorX", color_X_);
+    colorXy->SetDouble("colorY", color_Y_);
+    state.Set("colorXy.colorSetting", std::move(colorXy));
+
+    device_->SetStateProperties(standardtraits::kComponent, state, nullptr);
+  }
+
+  void AbortCommand(std::shared_ptr<weave::Command>& cmd) {
+    weave::ErrorPtr error;
+    weave::Error::AddTo(&error, FROM_HERE, "invalidParameterValue",
+                        "Invalid parameters");
+    cmd->Abort(error.get(), nullptr);
+  }
+
+  weave::Device* device_{nullptr};
+
+  // Simulate the state of the testdevice.
+  weave::lockstate::LockState lock_state_{weave::lockstate::LockState::kLocked};
+  bool light_status_{false};
+  double brightness_state_{0.0};
+  int32_t color_temp_{0};
+  int32_t color_temp_min_value_{0};
+  int32_t color_temp_max_value_{1};
+  double color_X_{0.0};
+  double color_Y_{0.0};
+  base::WeakPtrFactory<TestDeviceHandler> weak_ptr_factory_{this};
+};
+
+int main(int argc, char** argv) {
+  Daemon::Options opts;
+  opts.model_id = "AOAAA";
+  if (!opts.Parse(argc, argv)) {
+    Daemon::Options::ShowUsage(argv[0]);
+    return 1;
+  }
+  Daemon daemon{opts};
+  TestDeviceHandler handler;
+  handler.Register(daemon.GetDevice());
+  daemon.Run();
+  return 0;
+}
diff --git a/tests_schema/tests_schema.mk b/tests_schema/tests_schema.mk
new file mode 100644
index 0000000..73dfefd
--- /dev/null
+++ b/tests_schema/tests_schema.mk
@@ -0,0 +1,45 @@
+# Copyright 2016 The Weave Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+###
+# test_schema
+
+TESTS_SCHEMA_DAEMON_SRC_FILES := \
+	tests_schema/daemon/testdevice/testdevice.cc
+
+tests_schema_daemon_obj_files := $(TESTS_SCHEMA_DAEMON_SRC_FILES:%.cc=out/$(BUILD_MODE)/%.o)
+
+$(tests_schema_daemon_obj_files) : $(LIBEVHTP_HEADERS)
+$(tests_schema_daemon_obj_files) : INCLUDES += $(LIBEVHTP_INCLUDES)
+$(tests_schema_daemon_obj_files) : out/$(BUILD_MODE)/%.o : %.cc
+	mkdir -p $(dir $@)
+	$(CXX) $(DEFS_$(BUILD_MODE)) $(INCLUDES) $(CFLAGS) $(CFLAGS_$(BUILD_MODE)) $(CFLAGS_CC) -c -o $@ $<
+
+tests_schema_daemon_common_flags := \
+	-Wl,-rpath=out/$(BUILD_MODE)/ \
+	-levent \
+	-levent_openssl \
+	-lpthread \
+	-lavahi-common \
+	-lavahi-client \
+	-lexpat \
+	-lcurl \
+	-lssl \
+	-lcrypto
+
+tests_schema_daemon_deps := out/$(BUILD_MODE)/examples_provider.a out/$(BUILD_MODE)/libweave.so
+
+ifeq (1, $(USE_INTERNAL_LIBEVHTP))
+tests_schema_daemon_deps += $(third_party_libevhtp_lib)
+else
+tests_schema_daemon_common_flags += -levhtp
+endif
+
+out/$(BUILD_MODE)/weave_daemon_testdevice : out/$(BUILD_MODE)/tests_schema/daemon/testdevice/testdevice.o $(tests_schema_daemon_deps)
+	$(CXX) -o $@ $^ $(CFLAGS) $(tests_schema_daemon_common_flags)
+
+all-testdevices : out/$(BUILD_MODE)/weave_daemon_testdevice
+
+.PHONY : all-testdevices
+
diff --git a/third_party/third_party.mk b/third_party/third_party.mk
index 18f9b98..e1da63c 100644
--- a/third_party/third_party.mk
+++ b/third_party/third_party.mk
@@ -94,3 +94,14 @@
 
 clean-libevhtp :
 	rm -rf $(third_party_libevhtp)
+
+# These settings are exported for other code to use as needed.
+USE_INTERNAL_LIBEVHTP ?= 1
+
+ifeq (1, $(USE_INTERNAL_LIBEVHTP))
+LIBEVHTP_INCLUDES = -Ithird_party/libevhtp -I$(dir $(third_party_libevhtp_header))
+LIBEVHTP_HEADERS = $(third_party_libevhtp_header)
+else
+LIBEVHTP_INCLUDES =
+LIBEVHTP_HEADERS =
+endif