1// Copyright 2015 The Weave Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "examples/daemon/common/daemon.h"
6
7#include <weave/device.h>
8#include <weave/provider/task_runner.h>
9
10#include <base/bind.h>
11#include <base/memory/weak_ptr.h>
12
13namespace {
14// Time for sensor temperature to match setting temperature
15const double kWarmUpTime = 60.0;
16// Oven max temp
17const double kMaxTemp = 300.0;
18// Oven min temp
19const double kMinTemp = 20.0;
20
21const char kTraits[] = R"({
22  "temperatureSetting": {
23    "commands": {
24      "setConfig": {
25        "minimalRole": "user",
26        "parameters": {
27          "units": {
28            "type": "string"
29          },
30          "tempSetting": {
31            "type": "number"
32          }
33        },
34        "errors": ["tempOutOfRange", "unsupportedUnits"]
35      }
36    },
37    "state": {
38      "supportedUnits": {
39        "type": "array",
40        "items": {
41          "type": "string",
42          "enum": [ "celsius", "fahrenheit", "kelvin" ]
43        },
44        "minItems": 1,
45        "uniqueItems": true,
46        "isRequired": true
47      },
48      "units": {
49        "type": "string",
50        "enum": [ "celsius", "fahrenheit", "kelvin" ],
51        "isRequired": true
52      },
53      "tempSetting": {
54        "type": "number",
55        "isRequired": true
56      },
57      "maxTempSetting": {
58        "type": "number",
59        "isRequired": true
60      },
61      "minTempSetting": {
62        "type": "number",
63        "isRequired": true
64      }
65    }
66  },
67  "temperatureSensor": {
68    "commands": {
69      "setConfig": {
70        "minimalRole": "user",
71        "parameters": {
72          "units": {
73            "type": "string"
74          }
75        },
76        "errors": ["unsupportedUnits"]
77      }
78    },
79    "state": {
80      "supportedUnits": {
81        "type": "array",
82        "items": {
83          "type": "string",
84          "enum": [
85            "celsius",
86            "fahrenheit",
87            "kelvin"
88          ]
89        },
90        "minItems": 1,
91        "uniqueItems": true,
92        "isRequired": true
93      },
94      "units": {
95        "type": "string",
96        "enum": [ "celsius", "fahrenheit", "kelvin" ],
97        "isRequired": true
98      },
99      "value": {
100        "type": "number",
101        "isRequired": true
102      }
103    }
104  },
105  "brightness": {
106    "commands": {
107      "setConfig": {
108        "minimalRole": "user",
109        "parameters": {
110          "brightness": {
111            "type": "integer",
112            "minimum": 0,
113            "maximum": 100
114          }
115        }
116      }
117    },
118    "state": {
119      "brightness": {
120        "type": "integer",
121        "isRequired": true,
122        "minimum": 0,
123        "maximum": 100
124      }
125    }
126  }
127})";
128
129const char kComponent[] = "oven";
130}  // anonymous namespace
131
132// OvenHandler is a virtual oven example
133// It implements the following commands from traits:
134// - temperatureSetting: sets the temperature for the oven
135// - brightness: sets the brightness of the oven light
136// It exposes the following states from traits:
137// - temperatureSetting: temperature setting for the oven
138// - temperatureSensor: current oven temperature
139// - brightness: current oven brightness
140class OvenHandler {
141 public:
142  OvenHandler(weave::provider::TaskRunner* task_runner)
143      : task_runner_{task_runner} {}
144
145  void Register(weave::Device* device) {
146    device_ = device;
147
148    device->AddTraitDefinitionsFromJson(kTraits);
149    CHECK(device->AddComponent(
150        kComponent, {"temperatureSetting", "temperatureSensor", "brightness"},
151        nullptr));
152
153    UpdateOvenState();
154
155    device->AddCommandHandler(kComponent, "temperatureSetting.setConfig",
156                              base::Bind(&OvenHandler::OnSetTempCommand,
157                                         weak_ptr_factory_.GetWeakPtr()));
158
159    device->AddCommandHandler(kComponent, "brightness.setConfig",
160                              base::Bind(&OvenHandler::OnSetBrightnessCommand,
161                                         weak_ptr_factory_.GetWeakPtr()));
162  }
163
164 private:
165  void OnSetTempCommand(const std::weak_ptr<weave::Command>& command) {
166    auto cmd = command.lock();
167    if (!cmd)
168      return;
169    LOG(INFO) << "received command: " << cmd->GetName();
170
171    const auto& params = cmd->GetParameters();
172    std::string units;
173    double temp;
174
175    if (params.GetString("units", &units) &&
176        params.GetDouble("tempSetting", &temp)) {
177      units_ = units;
178      target_temperature_ = temp;
179
180      UpdateOvenState();
181
182      cmd->Complete({}, nullptr);
183      LOG(INFO) << cmd->GetName() << " updated oven, matching temp";
184
185      if (target_temperature_ != current_temperature_ && !is_match_ticking_) {
186        double tickIncrement =
187            ((target_temperature_ - current_temperature_) / kWarmUpTime);
188        DoTick(tickIncrement);
189      }
190      return;
191    }
192
193    weave::ErrorPtr error;
194    weave::Error::AddTo(&error, FROM_HERE, "invalid_parameter_value",
195                        "Invalid parameters");
196    cmd->Abort(error.get(), nullptr);
197  }
198
199  void OnSetBrightnessCommand(const std::weak_ptr<weave::Command>& command) {
200    auto cmd = command.lock();
201    if (!cmd)
202      return;
203    LOG(INFO) << "received command: " << cmd->GetName();
204
205    const auto& params = cmd->GetParameters();
206
207    int brightness;
208    if (params.GetInteger("brightness", &brightness)) {
209      brightness_ = brightness;
210
211      UpdateOvenState();
212
213      cmd->Complete({}, nullptr);
214      return;
215    }
216
217    weave::ErrorPtr error;
218    weave::Error::AddTo(&error, FROM_HERE, "invalid_parameter_value",
219                        "Invalid parameters");
220    cmd->Abort(error.get(), nullptr);
221  }
222
223  void UpdateOvenState() {
224    base::DictionaryValue state;
225    base::ListValue supportedUnits;
226    supportedUnits.AppendStrings({"celsius"});
227
228    state.SetString("temperatureSensor.units", units_);
229    state.SetDouble("temperatureSensor.value", current_temperature_);
230    state.Set("temperatureSensor.supportedUnits", supportedUnits.DeepCopy());
231
232    state.SetString("temperatureSetting.units", units_);
233    state.SetDouble("temperatureSetting.tempSetting", target_temperature_);
234    state.Set("temperatureSetting.supportedUnits", supportedUnits.DeepCopy());
235    state.SetDouble("temperatureSetting.maxTempSetting", kMaxTemp);
236    state.SetDouble("temperatureSetting.minTempSetting", kMinTemp);
237
238    state.SetInteger("brightness.brightness", brightness_);
239
240    device_->SetStateProperties(kComponent, state, nullptr);
241  }
242
243  void DoTick(double tickIncrement) {
244    LOG(INFO) << "Oven matching temp tick";
245
246    if (std::fabs(target_temperature_ - current_temperature_) >=
247        tickIncrement) {
248      is_match_ticking_ = true;
249      current_temperature_ += tickIncrement;
250      UpdateOvenState();
251      task_runner_->PostDelayedTask(
252          FROM_HERE, base::Bind(&OvenHandler::DoTick,
253                                weak_ptr_factory_.GetWeakPtr(), tickIncrement),
254          base::TimeDelta::FromSeconds(1));
255      return;
256    }
257
258    is_match_ticking_ = false;
259    current_temperature_ = target_temperature_;
260    UpdateOvenState();
261
262    LOG(INFO) << "Oven temp matched";
263  }
264
265  weave::Device* device_{nullptr};
266  weave::provider::TaskRunner* task_runner_{nullptr};
267
268  std::string units_ = "celsius";
269  double target_temperature_ = 0.0;
270  double current_temperature_ = 0.0;
271  int brightness_ = 0;
272  bool is_match_ticking_ = false;
273
274  base::WeakPtrFactory<OvenHandler> weak_ptr_factory_{this};
275};
276
277int main(int argc, char** argv) {
278  Daemon::Options opts;
279  if (!opts.Parse(argc, argv)) {
280    Daemon::Options::ShowUsage(argv[0]);
281    return 1;
282  }
283  Daemon daemon{opts};
284  OvenHandler handler{daemon.GetTaskRunner()};
285  handler.Register(daemon.GetDevice());
286  daemon.Run();
287  return 0;
288}
289